From 98e382d6c5a4e9f0b81de226571b20925487850f Mon Sep 17 00:00:00 2001
From: Ben Brachmann <49547103+bevenio@users.noreply.github.com>
Date: Wed, 30 Oct 2024 11:01:51 +0100
Subject: [PATCH] Squashed commit of the following:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit df58baff01da6946cd7f075a10790cd1c52ea1f0
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Tue Oct 29 21:05:07 2024 -0400

    Cookie Sync: Use max when limit is 0 (#4022)

commit db2a872e4e1ceead524d2894013f298c8b499533
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Tue Oct 29 17:20:57 2024 -0400

    ORTB 2.6: Full support with down convert for 2.5 adapters (#4019)

    Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com>
    Co-authored-by: Veronika Solovei <kalypsonika@gmail.com>

commit 14197618936578fda6f019e21fa7b40504abc083
Author: Scott Kay <noreply@syntaxnode.com>
Date:   Tue Oct 29 16:35:05 2024 -0400

    Rename Blacklisted Apps to Blocked Apps (#3620)

commit ddf897c861bfb9cd026834161c43e53287120ff1
Author: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com>
Date:   Tue Oct 29 14:07:49 2024 -0400

    Update Cookie Sync Chooser Debug Message to match Java (#3558)

commit a23380f23fd5792c951f0440c7a55a8e055cc839
Author: Mohammad Nurul Islam Shihan <93646635+ishihanvcs@users.noreply.github.com>
Date:   Tue Oct 29 21:34:31 2024 +0600

    ImproveDigital: Remove placementKey & addtlconsent parsing (#3728)

commit 03a4abdf04b3176fcb394ca173fedd1b6beba0ff
Author: Alex Maltsev <and1sscsgo@gmail.com>
Date:   Tue Oct 22 22:26:45 2024 +0300

    Sample: Fix prebid js loading bug on sample html page (#3792)

commit 18f679834a60cdb243afd3b7a17fd10dc020a53f
Author: Sebastien Boisvert <sebhtml@protonmail.com>
Date:   Tue Oct 22 15:15:07 2024 -0400

    Bump Go version to 1.22 in dev containers config (#3983)

commit bcf6491f94c685fefc22f222e96c65a5cdd38344
Author: sindhuja-sridharan <148382298+sindhuja-sridharan@users.noreply.github.com>
Date:   Thu Oct 17 15:20:45 2024 -0600

    GumGum: Declare ORTB 2.6 support (#3985)

commit 451bc449e09be5aa5310e865bc3904fa92ad3474
Author: Bluesea <129151981+blueseasx@users.noreply.github.com>
Date:   Thu Oct 17 03:12:50 2024 +0800

    BlueSea: Add site capability (#3910)

    Co-authored-by: prebid-bluesea <prebid@blueseasx.com>

commit 8134328c03de3b8d4618c027dd6caf1c063a00a6
Author: Boris Yu <admin@xdevel.info>
Date:   Wed Oct 16 21:08:50 2024 +0300

    Displayio: Make imp.bidfloor optional (#3959)

commit b56923c28d20f05cc0b8d4d0a340233b4ed03840
Author: Piotr Jaworski <109736938+piotrj-rtbh@users.noreply.github.com>
Date:   Tue Oct 15 21:49:39 2024 +0200

    RTB House: Resolve AUCTION_PRICE macro (#3901)

commit cbe987664b8b36a63620ac9fd88ef3a4731c1892
Author: Irakli Gotsiridze <ikagotso@gmail.com>
Date:   Tue Oct 15 23:30:17 2024 +0400

    enable gzip (#3946)

commit 5fcbbbfb17d4029bf60f03c7919490dfcdd98874
Author: Patrick Loughrey <ploughrey@triplelift.com>
Date:   Tue Oct 15 15:19:47 2024 -0400

    Triplelift: Adding flag for 2.6 support (#3966)

commit c37951a3be2494623bffbed352137eed98bf7b1b
Author: ym-winston <46379634+ym-winston@users.noreply.github.com>
Date:   Tue Oct 15 15:18:54 2024 -0400

    update yieldmo.yaml to indicate support for ortb 2.6 (#3968)

commit 9bb9b3db2135b0085e8b1f3608eb1da73871e6b7
Author: bhainesnexxen <146981622+bhainesnexxen@users.noreply.github.com>
Date:   Tue Oct 15 12:09:33 2024 -0700

    Unruly: Indicate Support for OpenRTB 2.6 (#3984)

commit 87d4412b6c8e01a011c8b258274a87c37c7760bc
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Wed Oct 9 19:08:14 2024 +0000

    Refactor: Move privacy init up (#3958)

commit 64584f60f0922ef9b285089469d667424e33d395
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Tue Oct 8 19:52:25 2024 +0000

    Refactor: Remove getAuctionBidderRequests (#3957)

commit d54c3ed22d437a590092870e611903208f840969
Author: ysfbsf <youssef.bsf@gmail.com>
Date:   Tue Oct 8 15:42:40 2024 +0200

    New Adapter: Missena (#3761)

commit 8689e0df95b9449a22d28eb5c56bbe9fc53f719c
Author: Nick <bansawbanchee@users.noreply.github.com>
Date:   Mon Oct 7 10:39:59 2024 -0400

    Sonobi: native and currency conversion support (#3889)

    Co-authored-by: bansawbanchee <nick@britepool.com>

commit f27bcefd0ffa351a1b844f26cc5d6250fd071c64
Author: dkornet-ad <169174147+dkornet-ad@users.noreply.github.com>
Date:   Thu Oct 3 09:33:54 2024 +0300

    New Adapter: Bidmatic (#3731)

    authored by @dkornet-ad

commit 6c154e0f7f86d63227026506cf38872f6fe61cb1
Author: Alexander Pykhteyev <alex.pykhteyev@gmail.com>
Date:   Wed Sep 25 23:08:23 2024 +0700

    New Adapter: Streamlyn  (#3900)

    Co-authored-by: apykhteyev <alex@project-limelight.com>

commit 53f51a6668ba7c1309915e5bd3feaabcac4cd771
Author: przemkaczmarek <167743744+przemkaczmarek@users.noreply.github.com>
Date:   Wed Sep 25 17:53:07 2024 +0200

    GPC: Set extension based on header (#3895)

commit c42fe53a22b85dc0d2d651963158e92e8ce288ce
Author: bkaneyama <brad.kaneyama@gmail.com>
Date:   Wed Sep 25 07:46:01 2024 -0700

    InMobi: mtype support (#3921)

commit 8b1b96e59003f7743d0e86301a87d9455e8592c1
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Wed Sep 25 14:22:18 2024 +0000

    Add docs build readme  (#3930)

commit 4462fcef3555e5d37cabecbbc12e8c98bd56ec30
Author: Eugene Dorfman <eugene.dorfman@gmail.com>
Date:   Wed Sep 25 16:11:45 2024 +0200

    51degrees module (#3893)

commit 11b6546f5bffe2737cdf0eb6c9a98d346e138504
Author: Alex Maltsev <and1sscsgo@gmail.com>
Date:   Mon Sep 23 16:54:18 2024 +0300

    Rubicon: Pass PBS host info to XAPI (#3903)

commit 93368cc9c4327be60622dae321e06bbfdb1e971f
Author: rtuschkany <35923908+rtuschkany@users.noreply.github.com>
Date:   Thu Sep 19 23:45:32 2024 +0200

    ConnectAd: String/int fix (#3925)

commit 905b3a5a29682ed74e9dc5e4c5e2404778ff9f08
Author: ShriprasadM <shriprasad.marathe@gmail.com>
Date:   Thu Sep 19 18:30:20 2024 +0530

    Log non bid reasons in bidder framework (#2891)

    Co-authored-by: Shriprasad Marathe <shriprasad.marathe@pubmatic.com>
    Co-authored-by: ashish.shinde <ashish.shinde@pubmatic.com>
    Co-authored-by: dhruv.sonone <dhruv.sonone@pubmatic.com>

commit 3c4527e9790e19647f12237f59875a5bbe61cfa3
Author: Mikael Lundin <mikael-lundin@users.noreply.github.com>
Date:   Wed Sep 18 13:38:49 2024 +0200

    Adnuntius: Return DSA in bid response (#3777)

commit 640b97c1e0c2286e98252794762926ad4fc8ce38
Author: lm-ved <105272141+lm-ved@users.noreply.github.com>
Date:   Mon Sep 16 12:17:54 2024 +0530

    LemmaDigital: change endpoint (#3862)

commit 3e24be7950bea48b960a3d68206083e4995cd97b
Author: Alexander Pykhteyev <alex.pykhteyev@gmail.com>
Date:   Sat Sep 14 04:04:09 2024 +0700

    New Adapter: TGM (#3848)

    Co-authored-by: apykhteyev <alex@project-limelight.com>

commit ffdd75f6fe1f24cf8ed7891b6af7b65afbda9761
Author: Robert Kawecki <rkaw92@users.noreply.github.com>
Date:   Thu Sep 12 21:52:51 2024 +0200

    New Adapter: AdTonos (#3853)

commit c02ee8c13280f72e173fee2adae441b92fb218ae
Author: Laurentiu Badea <laurb9@users.noreply.github.com>
Date:   Thu Sep 12 12:34:46 2024 -0700

    Update exchange json tests with correct hb_pb_cat_dur  (#3836)

commit 7613ff5a2aa304522854ac1b61dafd780caea8d7
Author: Laurentiu Badea <laurb9@users.noreply.github.com>
Date:   Thu Sep 12 12:27:07 2024 -0700

    Update adapter json test framework to validate BidVideo (#3835)

commit 6cbedf0319f6b82946bfde02f8cdc4875f518a69
Author: schubert-sc <144821265+schubert-sc@users.noreply.github.com>
Date:   Wed Sep 11 19:41:53 2024 +0300

    Smartx: Declare OpenRTB 2.6 support (#3896)

commit e0a21d0c355ccc6cc903259a3435c7939151e66c
Author: qt-io <104574052+qt-io@users.noreply.github.com>
Date:   Wed Sep 11 19:31:58 2024 +0300

    QT: Add tcfeu support (#3892)

    Co-authored-by: qt-io <qtssp-support@qt.io>

commit b920cca0c426eed97e44ea17d28dfbf4dd8f868d
Author: Yanivplaydigo <165155195+Yanivplaydigo@users.noreply.github.com>
Date:   Wed Sep 11 19:19:12 2024 +0300

    Playdigo: Add tcfeu support (#3890)

commit 6a011ed23d02a181d9ed487c105967ed91b98edf
Author: ccorbo <ccorbo2013@gmail.com>
Date:   Wed Sep 11 09:53:54 2024 -0400

    Update github.com/rs/cors to v1.11.0 (#3884)

    Co-authored-by: Chris Corbo <chris.corbo@indexexchange.com>

commit ec6a45d2b6772b32c04053504a3422c571f9fb06
Author: Steffen Müller <449563+steffenmllr@users.noreply.github.com>
Date:   Wed Sep 4 15:51:44 2024 +0200

    Agma: Allow app.bundle to be used as selector for apps (#3780)

commit 8237f7fec597ac15b893eb2f8715744cfaee4590
Author: Scott Kay <noreply@syntaxnode.com>
Date:   Tue Sep 3 14:34:56 2024 -0400

    Refactor Bid Splitter Privacy Functions (#3645)

commit f7caea51241fa0ac427d4789a60efbff9b36b9ab
Author: Brian Schmidt <brian.schmidt@openx.com>
Date:   Fri Aug 30 06:47:45 2024 -0700

    OpenX: indicate support for OpenRTB 2.6 (#3879)

commit e825553a70ce9143a821c851eb7d77b2b01a2830
Author: Ben Oraki <46795400+BenOraki@users.noreply.github.com>
Date:   Fri Aug 30 16:36:22 2024 +0300

    New Adapter: Oraki (#3839)

commit 2e2b49fb166c0b6f95692a58ca662a8cf3d178a9
Author: escalax <management@escalax.io>
Date:   Fri Aug 30 16:28:04 2024 +0300

    New Adapter: Escalax (#3798)

commit 4ea0e33a37c77b2167c72a1448a975d1841fb58a
Author: Copper6SSP <info@copper6.com>
Date:   Fri Aug 30 16:18:46 2024 +0300

    New Adapter: Copper6SSP (#3755)

commit 8d7117d948879d0cf44b2513a9beec3bf6404012
Author: Brian Sardo <1168933+bsardo@users.noreply.github.com>
Date:   Tue Aug 27 13:23:34 2024 -0400

    Revert "New Module: 51Degrees (#3650)" (#3888)

    This reverts commit 2606e7529f36aca8e645fc38814ba9924baba8a9.

commit 84a8162205b1aacaec18ceedd9cb0b99796818b7
Author: Anand Venkatraman <avenkatraman@pulsepoint.com>
Date:   Thu Aug 22 16:14:32 2024 +0530

    PulsePoint: ortb 2.6 version and gpp support (#3874)

    authored by @anand-venkatraman

commit bd85ba414df00ba971015773ef67ccecb6f7792c
Author: Nick Llerandi <nick.llerandi@kargo.com>
Date:   Thu Aug 22 02:15:12 2024 -0400

    specifies ortb 2.6 support (#3) (#3876)

commit 54f875981f54964410b9e5370a421f7d25af116f
Author: dtbarne <7635750+dtbarne@users.noreply.github.com>
Date:   Wed Aug 21 07:26:30 2024 -0500

    Update mobilefuse.yaml to indicate support for OpenRTB 2.6 and GPP (#3871)

commit 59a5b07ed07f91fec6e51ee21e57e1de6099d4b5
Author: mwang-sticky <mwang@freewheel.tv>
Date:   Wed Aug 21 20:26:15 2024 +0800

    freewheel-adapter: support 2.6 (#3873)

commit a556e2d3479d53509dffd21bd0e23689332a87a8
Author: Dubyk Danylo <45672370+CTMBNara@users.noreply.github.com>
Date:   Wed Aug 21 15:23:39 2024 +0300

    Fix currency conversion bug. (#3867)

    Co-authored-by: ddubyk <ddubyk@magnite.com>

commit d16171226aacf9d3aeaf4426c10e499fcd57d9eb
Author: Pubrise <prebid@pubrise.ai>
Date:   Wed Aug 21 12:43:39 2024 +0300

    new adapter (#3833)

    authored by @Pubrise

commit e8509e659034f406b1aa0c728742b63ea565a04e
Author: bretg <bgorsline@gmail.com>
Date:   Wed Aug 21 05:40:26 2024 -0400

    declare support for ORTB 2.6 (#3872)

    authored by @bretg

commit 6be724459440c9e8fdc2f94eeff5df8280c8750e
Author: Saar Amrani <saar120@gmail.com>
Date:   Wed Aug 21 12:37:59 2024 +0300

    Update Vidazoo bidder info for GPP support (#3869)

commit e4bd6d3675111a973c100694af84f6764fc21f1e
Author: gg-natalia <148577437+gg-natalia@users.noreply.github.com>
Date:   Sun Aug 18 06:52:28 2024 -0300

    ADTS-455 remove video validations (#3842)

    authored by @gg-natalia

commit 2606e7529f36aca8e645fc38814ba9924baba8a9
Author: James Rosewell <james@51degrees.com>
Date:   Fri Aug 16 14:20:22 2024 +0100

    New Module: 51Degrees (#3650)

    Co-authored-by: James Rosewell <james@51degrees.com>
    Co-authored-by: Marin Miletic <mrnmiletic@gmail.com>
    Co-authored-by: Sarana-Anna <anna.sarana@gmail.com>
    Co-authored-by: Eugene Dorfman <eugene.dorfman@gmail.com>
    Co-authored-by: Krasilchuk Yaroslav <legend.ko@hotmail.com>

commit 0e9b234a8b6faa7a3a105ab5662aa047c8798574
Author: Antonios Sarhanis <tsarhanis@gmail.com>
Date:   Tue Aug 13 18:28:44 2024 +1000

    Use format=prebid on adserver requests. (#3846)

commit 4d64623dd680bfd9add95fbf2c65b5b224f2b64a
Author: ownAdx <135326256+ownAdx-prebid@users.noreply.github.com>
Date:   Mon Aug 12 18:51:53 2024 +0530

    OwnAdx: Bidder param and URL updates (#3813)

    Co-authored-by: Hina Yadav <hina.yadav@vertoz.com>

commit 2a19924a0076a6a501acb47ec42f80ba5bc19abc
Author: ahmadlob <109217988+ahmadlob@users.noreply.github.com>
Date:   Mon Aug 12 16:09:23 2024 +0300

    taboola-support-app-in-prebid-server (#3795)

commit 4f177cae3b1ca96a9e9ed5145610eb6ab9ada42e
Author: benben2001 <145416009+benben2001@users.noreply.github.com>
Date:   Mon Aug 12 21:07:38 2024 +0800

    New Adapter: MeloZen (#3784)
---
 .devcontainer/devcontainer.json               |    2 +-
 Dockerfile                                    |    1 -
 README.md                                     |    5 +
 adapters/adapterstest/test_json.go            |   19 +-
 adapters/adnuntius/adnuntius.go               |   50 +-
 .../exemplary/simple-banner.json              |    2 +-
 .../supplemental/check-dealId.json            |    2 +-
 ...heck-dsa-advertiser-legalName-omitted.json |  133 ++
 .../check-dsa-advertiser-legalName.json       |  134 ++
 .../check-dsa-advertiser-omitted.json         |  123 ++
 .../supplemental/check-gdpr.json              |    2 +-
 .../supplemental/check-gross-bids.json        |    2 +-
 .../supplemental/check-net-bids.json          |    2 +-
 .../check-noCookies-parameter.json            |    2 +-
 .../supplemental/check-noCookies.json         |    2 +-
 .../supplemental/check-order-multi-imp.json   |    6 +-
 .../supplemental/check-userId.json            |    2 +-
 .../supplemental/empty-regs-ext.json          |    2 +-
 .../supplemental/empty-regs.json              |    2 +-
 .../supplemental/height-error.json            |    2 +-
 .../supplemental/invalid-regs-ext.json        |   46 +
 .../supplemental/max-deals-test.json          |    4 +-
 .../supplemental/send-header-information.json |    2 +-
 .../adnuntiustest/supplemental/site-ext.json  |  114 +
 .../supplemental/size-check.json              |    2 +-
 .../supplemental/status-400.json              |    2 +-
 .../supplemental/test-networks.json           |    2 +-
 .../adnuntiustest/supplemental/user-ext.json  |  113 +
 .../supplemental/width-error.json             |    2 +-
 adapters/adtonos/adtonos.go                   |  142 ++
 adapters/adtonos/adtonos_test.go              |   30 +
 .../exemplary/simple-audio-with-mtype.json    |  115 +
 .../adtonostest/exemplary/simple-audio.json   |  113 +
 .../adtonostest/exemplary/simple-video.json   |  113 +
 .../wrong-impression-mapping.json             |  103 +
 adapters/adtonos/params_test.go               |   43 +
 adapters/bidmatic/bidmatic.go                 |  206 ++
 adapters/bidmatic/bidmatic_test.go            |   23 +
 .../exemplary/media-type-mapping.json         |   91 +
 .../bidmatictest/exemplary/simple-banner.json |   98 +
 .../bidmatictest/exemplary/simple-video.json  |   57 +
 .../supplemental/explicit-dimensions.json     |   60 +
 .../supplemental/imp-ext-empty.json           |   21 +
 .../supplemental/wrong-impression-ext.json    |   26 +
 .../wrong-impression-mapping.json             |   79 +
 .../supplemental/wrong-response.json          |   65 +
 adapters/bidmatic/params_test.go              |   64 +
 .../blueseatest/exemplary/site-banner.json    |  133 ++
 .../blueseatest/exemplary/site-native.json    |  133 ++
 .../blueseatest/exemplary/site-video.json     |  163 ++
 adapters/connectad/connectad.go               |    2 +-
 adapters/copper6ssp/copper6ssp.go             |  157 ++
 adapters/copper6ssp/copper6ssp_test.go        |   20 +
 .../copper6ssptest/exemplary/endpointId.json  |  136 ++
 .../exemplary/multi-format.json               |  105 +
 .../copper6ssptest/exemplary/multi-imp.json   |  253 +++
 .../exemplary/simple-banner.json              |  136 ++
 .../exemplary/simple-native.json              |  120 ++
 .../exemplary/simple-video.json               |  131 ++
 .../exemplary/simple-web-banner.json          |  136 ++
 .../supplemental/bad_media_type.json          |   83 +
 .../supplemental/bad_response.json            |   85 +
 .../supplemental/no-valid-bidder-param.json   |   42 +
 .../supplemental/no-valid-imp-ext.json        |   38 +
 .../supplemental/status-204.json              |   80 +
 .../supplemental/status-not-200.json          |   85 +
 adapters/copper6ssp/params_test.go            |   47 +
 adapters/displayio/displayio.go               |  188 ++
 adapters/escalax/escalax.go                   |  162 ++
 adapters/escalax/escalax_test.go              |   28 +
 .../escalaxtest/exemplary/banner-app.json     |  155 ++
 .../escalaxtest/exemplary/banner-web.json     |  203 ++
 .../escalaxtest/exemplary/native-app.json     |  151 ++
 .../escalaxtest/exemplary/native-web.json     |  138 ++
 .../escalaxtest/exemplary/video-app.json      |  164 ++
 .../escalaxtest/exemplary/video-web.json      |  162 ++
 .../supplemental/bad_media_type.json          |  139 ++
 .../supplemental/empty-seatbid-array.json     |  133 ++
 .../invalid-bidder-ext-object.json            |   33 +
 .../supplemental/invalid-ext-object.json      |   31 +
 .../supplemental/invalid-response.json        |  115 +
 .../supplemental/status-code-bad-request.json |   96 +
 .../supplemental/status-code-other-error.json |   84 +
 adapters/escalax/params_test.go               |   52 +
 .../freewheelssptest/exemplary/multi-imp.json |   33 +-
 adapters/gumgum/gumgum.go                     |   15 +-
 .../supplemental/missing-video-params.json    |   36 -
 .../supplemental/video-missing-size.json      |   35 -
 .../supplemental/video-partial-size.json      |   77 +-
 .../supplemental/video-zero-size.json         |   78 +-
 adapters/improvedigital/improvedigital.go     |   83 +-
 .../addtl-consent-multi-tilda.json            |   94 -
 .../supplemental/addtl-consent.json           |   94 -
 adapters/improvedigital/params_test.go        |    3 +-
 adapters/inmobi/inmobi.go                     |   28 +-
 .../exemplary/simple-app-banner.json          |    4 +-
 .../exemplary/simple-app-native.json          |    4 +-
 .../exemplary/simple-app-video.json           |    4 +-
 .../exemplary/simple-web-banner.json          |    4 +-
 .../exemplary/simple-web-native.json          |  106 +
 .../exemplary/simple-web-video.json           |    4 +-
 .../supplemental/banner-format-coersion.json  |    4 +-
 .../supplemental/invalid-mtype.json           |   97 +
 adapters/lemmadigital/lemmadigital_test.go    |    2 +-
 .../lemmadigitaltest/exemplary/banner.json    |    2 +-
 .../lemmadigitaltest/exemplary/multi-imp.json |    2 +-
 .../lemmadigitaltest/exemplary/video.json     |    2 +-
 .../supplemental/empty-seatbid-array.json     |    2 +-
 .../supplemental/invalid-response.json        |    2 +-
 .../supplemental/status-code-bad-request.json |    2 +-
 .../supplemental/status-code-no-content.json  |    2 +-
 .../supplemental/status-code-other-error.json |    2 +-
 adapters/melozen/melozen.go                   |  185 ++
 adapters/melozen/melozen_test.go              |   30 +
 .../melozentest/exemplary/app-banner.json     |  132 ++
 .../melozentest/exemplary/app-native.json     |  100 +
 .../melozentest/exemplary/app-video.json      |  137 ++
 .../melozentest/exemplary/multi-imps.json     |  239 +++
 .../melozentest/exemplary/web-banner.json     |  138 ++
 .../melozentest/exemplary/web-video.json      |  129 ++
 .../supplemental/bad-media-type-request.json  |   28 +
 .../melozentest/supplemental/no-fill.json     |   90 +
 .../supplemental/response-status-400.json     |   95 +
 .../supplemental/response-status-not-200.json |   84 +
 .../supplemental/wrong-bid-ext.json           |   85 +
 adapters/melozen/params_test.go               |   50 +
 adapters/missena/missena.go                   |  215 ++
 adapters/missena/missena_test.go              |   21 +
 .../missenatest/exemplary/multiple-imps.json  |  129 ++
 .../exemplary/simple-banner-ipv6.json         |  105 +
 .../missenatest/exemplary/simple-banner.json  |  105 +
 .../exemplary/valid-imp-error-imp.json        |  129 ++
 .../supplemental/error-ext-bidder.json        |   25 +
 .../supplemental/error-imp-ext.json           |   23 +
 .../missenatest/supplemental/status-204.json  |   83 +
 .../missenatest/supplemental/status-400.json  |   89 +
 .../supplemental/status-not-200.json          |   89 +
 adapters/missena/params_test.go               |   50 +
 adapters/openx/openx.go                       |    2 +-
 .../openxtest/exemplary/video-rewarded.json   |    7 +-
 adapters/oraki/oraki.go                       |  152 ++
 adapters/oraki/oraki_test.go                  |   20 +
 .../oraki/orakitest/exemplary/endpointId.json |  136 ++
 .../orakitest/exemplary/multi-format.json     |  105 +
 .../oraki/orakitest/exemplary/multi-imp.json  |  253 +++
 .../orakitest/exemplary/simple-banner.json    |  136 ++
 .../orakitest/exemplary/simple-native.json    |  120 ++
 .../orakitest/exemplary/simple-video.json     |  131 ++
 .../exemplary/simple-web-banner.json          |  136 ++
 .../supplemental/bad_media_type.json          |   83 +
 .../orakitest/supplemental/bad_response.json  |   85 +
 .../orakitest/supplemental/status-204.json    |   80 +
 .../supplemental/status-not-200.json          |   85 +
 adapters/oraki/params_test.go                 |   47 +
 adapters/ownadx/ownadx.go                     |    9 +-
 adapters/ownadx/ownadx_test.go                |    2 +-
 .../pubmatictest/exemplary/video.json         |    3 +-
 adapters/pubrise/params_test.go               |   47 +
 adapters/pubrise/pubrise.go                   |  159 ++
 adapters/pubrise/pubrise_test.go              |   20 +
 .../pubrisetest/exemplary/endpointId.json     |  136 ++
 .../pubrisetest/exemplary/multi-format.json   |  105 +
 .../pubrisetest/exemplary/multi-imp.json      |  253 +++
 .../pubrisetest/exemplary/simple-banner.json  |  136 ++
 .../pubrisetest/exemplary/simple-native.json  |  120 ++
 .../pubrisetest/exemplary/simple-video.json   |  131 ++
 .../exemplary/simple-web-banner.json          |  136 ++
 .../supplemental/bad_media_type.json          |   83 +
 .../supplemental/bad_response.json            |   85 +
 .../supplemental/no-valid-impressions.json    |   20 +
 .../pubrisetest/supplemental/status-204.json  |   80 +
 .../supplemental/status-not-200.json          |   85 +
 adapters/rtbhouse/rtbhouse.go                 |   12 +-
 .../exemplary/banner-resolve-macros.json      |   87 +
 adapters/rubicon/rubicon.go                   |   18 +-
 adapters/rubicon/rubicon_test.go              |   77 +-
 .../exemplary/25-26-transition-period.json    |    5 +-
 .../rubicontest/exemplary/app-imp-fpd.json    |    5 +-
 .../exemplary/bidonmultiformat.json           |   10 +-
 .../exemplary/flexible-schema.json            |    5 +-
 .../exemplary/hardcode-secure.json            |    5 +-
 .../exemplary/non-bidonmultiformat.json       |    5 +-
 .../rubicontest/exemplary/simple-banner.json  |    5 +-
 .../rubicontest/exemplary/simple-native.json  |    5 +-
 .../rubicontest/exemplary/simple-video.json   |    5 +-
 .../rubicontest/exemplary/site-imp-fpd.json   |    5 +-
 .../rubicontest/exemplary/user-fpd.json       |    5 +-
 .../supplemental/no-site-content-data.json    |    5 +-
 .../supplemental/no-site-content.json         |    5 +-
 .../supplemental/no-app-site-request.json     |   30 -
 adapters/sonobi/sonobi.go                     |   39 +-
 .../sonobi/sonobitest/exemplary/banner.json   |    2 +
 .../sonobi/sonobitest/exemplary/native.json   |  143 ++
 .../sonobi/sonobitest/exemplary/no-bid.json   |    2 +
 .../supplemental/currency-conversion.json     |  172 ++
 adapters/taboola/taboola.go                   |   27 +-
 .../bannerAppRequest.json}                    |   15 +-
 .../yeahmobitest/exemplary/simple-video.json  |   43 +-
 amp/parse.go                                  |    2 +-
 amp/parse_test.go                             |   40 +-
 analytics/agma/README.md                      |   28 +
 analytics/agma/agma_module.go                 |  271 +++
 analytics/agma/agma_module_test.go            |  735 +++++++
 analytics/pubstack/pubstack_module_test.go    |    2 +-
 config/config.go                              |   17 +-
 config/config_test.go                         |   12 +-
 docs/build/README.md                          |  110 +
 endpoints/cookie_sync.go                      |   53 +-
 endpoints/cookie_sync_test.go                 |  276 ++-
 endpoints/events/event.go                     |    2 +-
 endpoints/openrtb2/amp_auction.go             |   58 +-
 endpoints/openrtb2/amp_auction_test.go        |  416 ++--
 endpoints/openrtb2/auction.go                 |  859 ++------
 endpoints/openrtb2/auction_benchmark_test.go  |    4 +-
 endpoints/openrtb2/auction_test.go            | 1870 ++++++++---------
 .../gdpr-ccpa-through-query.json              |    4 +-
 ...dpr-legacy-tcf2-consent-through-query.json |    8 +-
 .../gdpr-tcf1-consent-through-query.json      |    4 +-
 .../gdpr-tcf2-consent-through-query.json      |    8 +-
 .../ortb-2.5-to-2.6-upconvert.json            |  306 +++
 .../blocked-app.json}                         |    4 +-
 .../invalid-whole/regs-ext-gdpr-string.json   |   48 -
 .../invalid-whole/regs-ext-malformed.json     |    2 +-
 ...pr-invalid.json => regs-gdpr-invalid.json} |    6 +-
 ...empty.json => user-eids-source-empty.json} |   13 +-
 ...mpty.json => user-eids-uids-id-empty.json} |   15 +-
 ...ssing.json => user-eids-uids-missing.json} |   12 +-
 .../invalid-whole/user-ext-consent-int.json   |    2 +-
 .../user-gdpr-consent-invalid.json            |    2 +-
 .../valid-whole/exemplary/device-sua.json     |  100 +
 .../exemplary/ortb-2.5-to-2.6-upconvert.json  |  393 ++++
 .../ortb-2.6-to-2.5-downconvert.json          |  417 ++++
 .../valid-whole/exemplary/source-schain.json  |   91 +
 .../supplementary/gdpr-conflict.json          |    5 +-
 .../supplementary/gdpr-conflict2.json         |    5 +-
 .../supplementary/us-privacy-invalid.json     |    4 +-
 endpoints/openrtb2/test_utils.go              |  140 +-
 endpoints/openrtb2/video_auction.go           |   42 +-
 endpoints/openrtb2/video_auction_test.go      |   15 +-
 endpoints/setuid_test.go                      |    4 +-
 errortypes/code.go                            |    2 +-
 errortypes/errortypes.go                      |   15 +-
 exchange/adapter_builders.go                  |   62 +-
 exchange/bidder.go                            |   21 +-
 exchange/bidder_test.go                       |  164 +-
 exchange/exchange.go                          |  108 +-
 exchange/exchange_test.go                     |  359 ++--
 .../exchangetest/append-bidder-names.json     |    8 +-
 .../exchangetest/ccpa-featureflag-on.json     |   11 +-
 exchange/exchangetest/debuglog_disabled.json  |    8 +-
 exchange/exchangetest/debuglog_enabled.json   |    8 +-
 .../exchangetest/eidpermissions-denied.json   |   22 +-
 .../firstpartydata-multibidder-user-eids.json |  239 +++
 ...firstpartydata-user-eids-req-user-nil.json |  203 ++
 ...stpartydata-user-nileids-req-user-nil.json |  163 ++
 .../exchangetest/generate-bid-id-error.json   |  203 ++
 .../exchangetest/generate-bid-id-many.json    |  170 ++
 .../exchangetest/generate-bid-id-one.json     |  129 ++
 .../exchangetest/include-brand-category.json  |    8 +-
 .../multi-bids-different-ortb-versions.json   |  276 +++
 .../exchangetest/schain-host-and-request.json |   39 +-
 exchange/exchangetest/schain-host-only.json   |   27 +-
 exchange/gdpr.go                              |   15 +-
 exchange/gdpr_test.go                         |   56 +-
 exchange/non_bid_reason.go                    |   50 +-
 exchange/non_bid_reason_test.go               |   65 +
 exchange/seat_non_bids.go                     |   56 +-
 exchange/seat_non_bids_test.go                |  529 ++++-
 exchange/utils.go                             |  770 +++----
 exchange/utils_test.go                        | 1739 +++++++++------
 gdpr/gdpr.go                                  |    4 +-
 gdpr/impl.go                                  |   33 +-
 gdpr/impl_test.go                             |   21 +-
 go.mod                                        |    7 +-
 go.sum                                        |   19 +-
 injector/injector_test.go                     |  455 ++++
 macros/macros.go                              |    4 +
 macros/provider.go                            |    5 +-
 macros/provider_test.go                       |    4 +-
 macros/string_index_based_replacer_test.go    |    2 +-
 metrics/config/metrics_test.go                |   25 +-
 metrics/go_metrics_test.go                    |    4 +-
 metrics/metrics.go                            |    8 +-
 metrics/prometheus/prometheus_test.go         |    8 +-
 modules/builder.go                            |    4 +
 .../fiftyonedegrees/devicedetection/README.md |  255 +++
 .../devicedetection/account_info_extractor.go |   37 +
 .../account_info_extractor_test.go            |   74 +
 .../devicedetection/account_validator.go      |   28 +
 .../devicedetection/account_validator_test.go |   71 +
 .../fiftyonedegrees/devicedetection/config.go |   80 +
 .../devicedetection/config_test.go            |  119 ++
 .../devicedetection/context.go                |    8 +
 .../devicedetection/device_detector.go        |  157 ++
 .../devicedetection/device_detector_test.go   |  190 ++
 .../devicedetection/device_info_extractor.go  |  121 ++
 .../device_info_extractor_test.go             |  130 ++
 .../devicedetection/evidence_extractor.go     |  118 ++
 .../evidence_extractor_test.go                |  256 +++
 .../devicedetection/fiftyone_device_types.go  |   77 +
 .../fiftyone_device_types_test.go             |   90 +
 .../hook_auction_entrypoint.go                |   27 +
 .../hook_raw_auction_request.go               |  173 ++
 .../fiftyonedegrees/devicedetection/models.go |   66 +
 .../devicedetection/models_test.go            |   63 +
 .../fiftyonedegrees/devicedetection/module.go |  107 +
 .../devicedetection/module_test.go            |  703 +++++++
 .../request_headers_extractor.go              |   47 +
 .../request_headers_extractor_test.go         |  118 ++
 .../devicedetection/sample/pbs.json           |   84 +
 .../devicedetection/sample/request_data.json  |  114 +
 .../devicedetection/sua_payload_extractor.go  |  144 ++
 openrtb_ext/bidders.go                        |   87 +-
 openrtb_ext/convert_down.go                   |    7 -
 openrtb_ext/convert_down_test.go              |   44 -
 openrtb_ext/imp_adtonos.go                    |    5 +
 openrtb_ext/imp_bidmatic.go                   |   11 +
 openrtb_ext/imp_connectad.go                  |    8 +-
 openrtb_ext/imp_copper6ssp.go                 |    6 +
 openrtb_ext/imp_escalax.go                    |    6 +
 openrtb_ext/imp_melozen.go                    |    5 +
 openrtb_ext/imp_missena.go                    |    7 +
 openrtb_ext/imp_oraki.go                      |    6 +
 openrtb_ext/imp_pubrise.go                    |    6 +
 openrtb_ext/request_wrapper.go                |  132 +-
 openrtb_ext/request_wrapper_test.go           |  104 +
 openrtb_ext/response.go                       |    8 +-
 ortb/clone.go                                 |   16 +
 ortb/clone_test.go                            |   41 +
 privacy/ccpa/consentwriter.go                 |   11 +-
 privacy/ccpa/consentwriter_test.go            |   11 +-
 privacy/ccpa/policy.go                        |   23 +-
 privacy/ccpa/policy_test.go                   |   37 +-
 privacy/gdpr/consentwriter.go                 |   26 +-
 privacy/gdpr/consentwriter_test.go            |   28 +-
 sample/001_banner/pbjs.html                   |  121 ++
 schain/schainwriter.go                        |   34 +-
 schain/schainwriter_test.go                   |  352 +++-
 static/bidder-info/adtonos.yaml               |   24 +
 static/bidder-info/bidmatic.yaml              |   18 +
 static/bidder-info/bluesea.yaml               |    6 +
 static/bidder-info/copper6ssp.yaml            |   21 +
 static/bidder-info/escalax.yaml               |   18 +
 static/bidder-info/freewheelssp.yaml          |    5 +-
 static/bidder-info/gumgum.yaml                |    3 +
 static/bidder-info/kargo.yaml                 |    1 +
 static/bidder-info/lemmadigital.yaml          |    8 +-
 static/bidder-info/melozen.yaml               |   19 +
 static/bidder-info/missena.yaml               |   16 +
 static/bidder-info/mobilefuse.yaml            |    3 +
 static/bidder-info/openx.yaml                 |    2 +
 static/bidder-info/oraki.yaml                 |   18 +
 static/bidder-info/ownadx.yaml                |    2 +-
 static/bidder-info/playdigo.yaml              |   24 +
 static/bidder-info/pubrise.yaml               |   21 +
 static/bidder-info/pulsepoint.yaml            |    3 +
 static/bidder-info/qt.yaml                    |   19 +
 static/bidder-info/rubicon.yaml               |    3 +
 static/bidder-info/smartx.yaml                |    2 +
 static/bidder-info/sonobi.yaml                |    5 +
 static/bidder-info/sovrn.yaml                 |    1 +
 static/bidder-info/streamlyn.yaml             |    2 +
 static/bidder-info/tgm.yaml                   |    1 +
 static/bidder-info/triplelift.yaml            |    8 +-
 static/bidder-info/triplelift_native.yaml     |    6 +-
 static/bidder-info/unruly.yaml                |    8 +-
 static/bidder-info/vidazoo.yaml               |   20 +
 static/bidder-info/yieldmo.yaml               |    2 +
 static/bidder-params/adtonos.json             |   14 +
 static/bidder-params/bidmatic.json            |   29 +
 static/bidder-params/copper6ssp.json          |   22 +
 static/bidder-params/escalax.json             |   22 +
 static/bidder-params/improvedigital.json      |   21 +-
 static/bidder-params/melozen.json             |   14 +
 static/bidder-params/missena.json             |   24 +
 static/bidder-params/oraki.json               |   22 +
 static/bidder-params/ownadx.json              |    7 +-
 static/bidder-params/pubrise.json             |   22 +
 stored_requests/data/by_id/accounts/test.json |    2 +-
 stored_responses/stored_responses.go          |   13 -
 stored_responses/stored_responses_test.go     |   66 -
 usersync/chooser.go                           |    6 +-
 usersync/chooser_test.go                      |    8 +-
 383 files changed, 26742 insertions(+), 4652 deletions(-)
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/site-ext.json
 create mode 100644 adapters/adnuntius/adnuntiustest/supplemental/user-ext.json
 create mode 100644 adapters/adtonos/adtonos.go
 create mode 100644 adapters/adtonos/adtonos_test.go
 create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json
 create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-audio.json
 create mode 100644 adapters/adtonos/adtonostest/exemplary/simple-video.json
 create mode 100644 adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json
 create mode 100644 adapters/adtonos/params_test.go
 create mode 100644 adapters/bidmatic/bidmatic.go
 create mode 100644 adapters/bidmatic/bidmatic_test.go
 create mode 100644 adapters/bidmatic/bidmatictest/exemplary/media-type-mapping.json
 create mode 100644 adapters/bidmatic/bidmatictest/exemplary/simple-banner.json
 create mode 100644 adapters/bidmatic/bidmatictest/exemplary/simple-video.json
 create mode 100644 adapters/bidmatic/bidmatictest/supplemental/explicit-dimensions.json
 create mode 100644 adapters/bidmatic/bidmatictest/supplemental/imp-ext-empty.json
 create mode 100644 adapters/bidmatic/bidmatictest/supplemental/wrong-impression-ext.json
 create mode 100644 adapters/bidmatic/bidmatictest/supplemental/wrong-impression-mapping.json
 create mode 100644 adapters/bidmatic/bidmatictest/supplemental/wrong-response.json
 create mode 100644 adapters/bidmatic/params_test.go
 create mode 100644 adapters/bluesea/blueseatest/exemplary/site-banner.json
 create mode 100644 adapters/bluesea/blueseatest/exemplary/site-native.json
 create mode 100644 adapters/bluesea/blueseatest/exemplary/site-video.json
 create mode 100644 adapters/copper6ssp/copper6ssp.go
 create mode 100644 adapters/copper6ssp/copper6ssp_test.go
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/endpointId.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/multi-format.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/multi-imp.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-banner.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-native.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-video.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/exemplary/simple-web-banner.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/bad_media_type.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/bad_response.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/no-valid-bidder-param.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/no-valid-imp-ext.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/status-204.json
 create mode 100644 adapters/copper6ssp/copper6ssptest/supplemental/status-not-200.json
 create mode 100644 adapters/copper6ssp/params_test.go
 create mode 100644 adapters/displayio/displayio.go
 create mode 100644 adapters/escalax/escalax.go
 create mode 100644 adapters/escalax/escalax_test.go
 create mode 100644 adapters/escalax/escalaxtest/exemplary/banner-app.json
 create mode 100644 adapters/escalax/escalaxtest/exemplary/banner-web.json
 create mode 100644 adapters/escalax/escalaxtest/exemplary/native-app.json
 create mode 100644 adapters/escalax/escalaxtest/exemplary/native-web.json
 create mode 100644 adapters/escalax/escalaxtest/exemplary/video-app.json
 create mode 100644 adapters/escalax/escalaxtest/exemplary/video-web.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/bad_media_type.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/empty-seatbid-array.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-bidder-ext-object.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-ext-object.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/invalid-response.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/status-code-bad-request.json
 create mode 100644 adapters/escalax/escalaxtest/supplemental/status-code-other-error.json
 create mode 100644 adapters/escalax/params_test.go
 delete mode 100644 adapters/gumgum/gumgumtest/supplemental/missing-video-params.json
 delete mode 100644 adapters/gumgum/gumgumtest/supplemental/video-missing-size.json
 delete mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent-multi-tilda.json
 delete mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent.json
 create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-native.json
 create mode 100644 adapters/inmobi/inmobitest/supplemental/invalid-mtype.json
 create mode 100644 adapters/melozen/melozen.go
 create mode 100644 adapters/melozen/melozen_test.go
 create mode 100644 adapters/melozen/melozentest/exemplary/app-banner.json
 create mode 100644 adapters/melozen/melozentest/exemplary/app-native.json
 create mode 100644 adapters/melozen/melozentest/exemplary/app-video.json
 create mode 100644 adapters/melozen/melozentest/exemplary/multi-imps.json
 create mode 100644 adapters/melozen/melozentest/exemplary/web-banner.json
 create mode 100644 adapters/melozen/melozentest/exemplary/web-video.json
 create mode 100644 adapters/melozen/melozentest/supplemental/bad-media-type-request.json
 create mode 100644 adapters/melozen/melozentest/supplemental/no-fill.json
 create mode 100644 adapters/melozen/melozentest/supplemental/response-status-400.json
 create mode 100644 adapters/melozen/melozentest/supplemental/response-status-not-200.json
 create mode 100644 adapters/melozen/melozentest/supplemental/wrong-bid-ext.json
 create mode 100644 adapters/melozen/params_test.go
 create mode 100644 adapters/missena/missena.go
 create mode 100644 adapters/missena/missena_test.go
 create mode 100644 adapters/missena/missenatest/exemplary/multiple-imps.json
 create mode 100644 adapters/missena/missenatest/exemplary/simple-banner-ipv6.json
 create mode 100644 adapters/missena/missenatest/exemplary/simple-banner.json
 create mode 100644 adapters/missena/missenatest/exemplary/valid-imp-error-imp.json
 create mode 100644 adapters/missena/missenatest/supplemental/error-ext-bidder.json
 create mode 100644 adapters/missena/missenatest/supplemental/error-imp-ext.json
 create mode 100644 adapters/missena/missenatest/supplemental/status-204.json
 create mode 100644 adapters/missena/missenatest/supplemental/status-400.json
 create mode 100644 adapters/missena/missenatest/supplemental/status-not-200.json
 create mode 100644 adapters/missena/params_test.go
 create mode 100644 adapters/oraki/oraki.go
 create mode 100644 adapters/oraki/oraki_test.go
 create mode 100644 adapters/oraki/orakitest/exemplary/endpointId.json
 create mode 100644 adapters/oraki/orakitest/exemplary/multi-format.json
 create mode 100644 adapters/oraki/orakitest/exemplary/multi-imp.json
 create mode 100644 adapters/oraki/orakitest/exemplary/simple-banner.json
 create mode 100644 adapters/oraki/orakitest/exemplary/simple-native.json
 create mode 100644 adapters/oraki/orakitest/exemplary/simple-video.json
 create mode 100644 adapters/oraki/orakitest/exemplary/simple-web-banner.json
 create mode 100644 adapters/oraki/orakitest/supplemental/bad_media_type.json
 create mode 100644 adapters/oraki/orakitest/supplemental/bad_response.json
 create mode 100644 adapters/oraki/orakitest/supplemental/status-204.json
 create mode 100644 adapters/oraki/orakitest/supplemental/status-not-200.json
 create mode 100644 adapters/oraki/params_test.go
 create mode 100644 adapters/pubrise/params_test.go
 create mode 100644 adapters/pubrise/pubrise.go
 create mode 100644 adapters/pubrise/pubrise_test.go
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/endpointId.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/multi-format.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/multi-imp.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-banner.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-native.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-video.json
 create mode 100644 adapters/pubrise/pubrisetest/exemplary/simple-web-banner.json
 create mode 100644 adapters/pubrise/pubrisetest/supplemental/bad_media_type.json
 create mode 100644 adapters/pubrise/pubrisetest/supplemental/bad_response.json
 create mode 100644 adapters/pubrise/pubrisetest/supplemental/no-valid-impressions.json
 create mode 100644 adapters/pubrise/pubrisetest/supplemental/status-204.json
 create mode 100644 adapters/pubrise/pubrisetest/supplemental/status-not-200.json
 create mode 100644 adapters/rtbhouse/rtbhousetest/exemplary/banner-resolve-macros.json
 delete mode 100644 adapters/smaato/smaatotest/supplemental/no-app-site-request.json
 create mode 100644 adapters/sonobi/sonobitest/exemplary/native.json
 create mode 100644 adapters/sonobi/sonobitest/supplemental/currency-conversion.json
 rename adapters/taboola/taboolatest/{supplemental/emptySiteInRequest.json => exemplary/bannerAppRequest.json} (92%)
 create mode 100644 analytics/agma/README.md
 create mode 100644 analytics/agma/agma_module.go
 create mode 100644 analytics/agma/agma_module_test.go
 create mode 100644 docs/build/README.md
 create mode 100644 endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json
 rename endpoints/openrtb2/sample-requests/{blacklisted/blacklisted-app.json => blocked/blocked-app.json} (96%)
 delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
 rename endpoints/openrtb2/sample-requests/invalid-whole/{regs-ext-gdpr-invalid.json => regs-gdpr-invalid.json} (86%)
 rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-uids-id-empty.json => user-eids-source-empty.json} (63%)
 rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-source-empty.json => user-eids-uids-id-empty.json} (62%)
 rename endpoints/openrtb2/sample-requests/invalid-whole/{user-ext-eids-uids-missing.json => user-eids-uids-missing.json} (70%)
 create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json
 create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json
 create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json
 create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json
 create mode 100644 exchange/exchangetest/firstpartydata-multibidder-user-eids.json
 create mode 100644 exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json
 create mode 100644 exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json
 create mode 100644 exchange/exchangetest/generate-bid-id-error.json
 create mode 100644 exchange/exchangetest/generate-bid-id-many.json
 create mode 100644 exchange/exchangetest/generate-bid-id-one.json
 create mode 100644 exchange/exchangetest/multi-bids-different-ortb-versions.json
 create mode 100644 exchange/non_bid_reason_test.go
 create mode 100644 injector/injector_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/README.md
 create mode 100644 modules/fiftyonedegrees/devicedetection/account_info_extractor.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/account_validator.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/account_validator_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/config.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/config_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/context.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/device_detector.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/device_detector_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/device_info_extractor.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/evidence_extractor.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/models.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/models_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/module.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/module_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/request_headers_extractor.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go
 create mode 100644 modules/fiftyonedegrees/devicedetection/sample/pbs.json
 create mode 100644 modules/fiftyonedegrees/devicedetection/sample/request_data.json
 create mode 100644 modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go
 create mode 100644 openrtb_ext/imp_adtonos.go
 create mode 100644 openrtb_ext/imp_bidmatic.go
 create mode 100644 openrtb_ext/imp_copper6ssp.go
 create mode 100644 openrtb_ext/imp_escalax.go
 create mode 100644 openrtb_ext/imp_melozen.go
 create mode 100644 openrtb_ext/imp_missena.go
 create mode 100644 openrtb_ext/imp_oraki.go
 create mode 100644 openrtb_ext/imp_pubrise.go
 create mode 100644 sample/001_banner/pbjs.html
 create mode 100644 static/bidder-info/adtonos.yaml
 create mode 100644 static/bidder-info/bidmatic.yaml
 create mode 100644 static/bidder-info/copper6ssp.yaml
 create mode 100644 static/bidder-info/escalax.yaml
 create mode 100644 static/bidder-info/melozen.yaml
 create mode 100644 static/bidder-info/missena.yaml
 create mode 100644 static/bidder-info/oraki.yaml
 create mode 100644 static/bidder-info/playdigo.yaml
 create mode 100644 static/bidder-info/pubrise.yaml
 create mode 100644 static/bidder-info/qt.yaml
 create mode 100644 static/bidder-info/streamlyn.yaml
 create mode 100644 static/bidder-info/tgm.yaml
 create mode 100644 static/bidder-info/vidazoo.yaml
 create mode 100644 static/bidder-params/adtonos.json
 create mode 100644 static/bidder-params/bidmatic.json
 create mode 100644 static/bidder-params/copper6ssp.json
 create mode 100644 static/bidder-params/escalax.json
 create mode 100644 static/bidder-params/melozen.json
 create mode 100644 static/bidder-params/missena.json
 create mode 100644 static/bidder-params/oraki.json
 create mode 100644 static/bidder-params/pubrise.json

diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index ebf6f614df3..f9bb82598ff 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -6,7 +6,7 @@
 		"dockerfile": "Dockerfile",
 		"args": {
 			// Update the VARIANT arg to pick a version of Go
-			"VARIANT": "1.20",
+			"VARIANT": "1.22",
 			// Options
 			"INSTALL_NODE": "false",
 			"NODE_VERSION": "lts/*"
diff --git a/Dockerfile b/Dockerfile
index e118e19562e..3fad605c87c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,6 @@ FROM golang:1.21-alpine
 RUN apk add --update tini 
 RUN mkdir -p /app/prebid-server/
 WORKDIR /app/prebid-server/
-
 COPY ./ ./
 
 RUN go mod download
diff --git a/README.md b/README.md
index 5db45115dec..0b1a02cd2bc 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,11 @@ or compile a standalone binary using the command:
 ``` bash
 go build .
 ```
+**Note:** if building from source there are a couple dependencies to be aware of: 
+1. *Compile-time*. Some modules ship native code that requires `cgo` (comes with the `go` compiler) being enabled - by default it is and environment variable `CGO_ENABLED=1` do NOT set it to `0`.
+2. *Compile-time*. `cgo` depends on the C-compiler, which usually is `gcc`, but can be changed by setting the value of `CC` env var, f.e. `CC=clang`.  On ubuntu `gcc` can be installed via `sudo apt-get install gcc`. 
+3. *Runtime*. Some modules require `libatomic`.  On ubuntu it is installed by running `sudo apt-get install libatomic1`.  `libatomic1` is a dependency of `gcc`, so if you are building with `gcc` and running on the same machine, it is likely that `libatomic1` is already installed.   
+
 Ensure that you deploy the `/static` directory, as Prebid Server requires those files at startup.
 
 ## Developing
diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go
index 5b6c56d2cee..b7358df6a5d 100644
--- a/adapters/adapterstest/test_json.go
+++ b/adapters/adapterstest/test_json.go
@@ -223,9 +223,10 @@ type expectedBidResponse struct {
 }
 
 type expectedBid struct {
-	Bid  json.RawMessage `json:"bid"`
-	Type string          `json:"type"`
-	Seat string          `json:"seat"`
+	Bid   json.RawMessage `json:"bid"`
+	Type  string          `json:"type"`
+	Seat  string          `json:"seat"`
+	Video json.RawMessage `json:"video,omitempty"`
 }
 
 // ---------------------------------------
@@ -330,6 +331,9 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec
 	assert.Equal(t, string(expected.Seat), string(actual.Seat), fmt.Sprintf(`%s.seat "%s" does not match expected "%s."`, description, string(actual.Seat), string(expected.Seat)))
 	assert.Equal(t, string(expected.Type), string(actual.BidType), fmt.Sprintf(`%s.type "%s" does not match expected "%s."`, description, string(actual.BidType), string(expected.Type)))
 	assert.NoError(t, diffOrtbBids(fmt.Sprintf("%s.bid", description), actual.Bid, expected.Bid))
+	if expected.Video != nil {
+		assert.NoError(t, diffBidVideo(fmt.Sprintf("%s.video", description), actual.BidVideo, expected.Video))
+	}
 }
 
 // diffOrtbBids compares the actual Bid made by the adapter to the expectation from the JSON file.
@@ -346,6 +350,15 @@ func diffOrtbBids(description string, actual *openrtb2.Bid, expected json.RawMes
 	return diffJson(description, actualJson, expected)
 }
 
+func diffBidVideo(description string, actual *openrtb_ext.ExtBidPrebidVideo, expected json.RawMessage) error {
+	actualJson, err := json.Marshal(actual)
+	if err != nil {
+		return fmt.Errorf("%s failed to marshal actual Bid Video into JSON. %v", description, err)
+	}
+
+	return diffJson(description, actualJson, []byte(expected))
+}
+
 // diffJson compares two JSON byte arrays for structural equality. It will produce an error if either
 // byte array is not actually JSON.
 func diffJson(description string, actual []byte, expected []byte) error {
diff --git a/adapters/adnuntius/adnuntius.go b/adapters/adnuntius/adnuntius.go
index 4b823455815..ff9c4e57aad 100644
--- a/adapters/adnuntius/adnuntius.go
+++ b/adapters/adnuntius/adnuntius.go
@@ -34,6 +34,11 @@ type extDeviceAdnuntius struct {
 	NoCookies bool `json:"noCookies,omitempty"`
 }
 
+type adnAdvertiser struct {
+	LegalName string `json:"legalName,omitempty"`
+	Name      string `json:"name,omitempty"`
+}
+
 type Ad struct {
 	Bid struct {
 		Amount   float64
@@ -53,6 +58,7 @@ type Ad struct {
 	LineItemId      string
 	Html            string
 	DestinationUrls map[string]string
+	Advertiser      adnAdvertiser `json:"advertiser,omitempty"`
 }
 
 type AdUnit struct {
@@ -159,7 +165,7 @@ func makeEndpointUrl(ortbRequest openrtb2.BidRequest, a *adapter, noCookies bool
 	}
 
 	q.Set("tzo", fmt.Sprint(tzo))
-	q.Set("format", "json")
+	q.Set("format", "prebid")
 
 	url := endpointUrl + "?" + q.Encode()
 	return url, nil
@@ -335,6 +341,40 @@ func getGDPR(request *openrtb2.BidRequest) (string, string, error) {
 	return gdpr, consent, nil
 }
 
+func generateReturnExt(ad Ad, request *openrtb2.BidRequest) (json.RawMessage, error) {
+	// We always force the publisher to render
+	var adRender int8 = 0
+
+	var requestRegsExt *openrtb_ext.ExtRegs
+	if request.Regs != nil && request.Regs.Ext != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &requestRegsExt); err != nil {
+
+			return nil, fmt.Errorf("Failed to parse Ext information in Adnuntius: %v", err)
+		}
+	}
+
+	if ad.Advertiser.Name != "" && requestRegsExt != nil && requestRegsExt.DSA != nil {
+		legalName := ad.Advertiser.Name
+		if ad.Advertiser.LegalName != "" {
+			legalName = ad.Advertiser.LegalName
+		}
+		ext := &openrtb_ext.ExtBid{
+			DSA: &openrtb_ext.ExtBidDSA{
+				AdRender: &adRender,
+				Paid:     legalName,
+				Behalf:   legalName,
+			},
+		}
+		returnExt, err := json.Marshal(ext)
+		if err != nil {
+			return nil, fmt.Errorf("Failed to parse Ext information in Adnuntius: %v", err)
+		}
+
+		return returnExt, nil
+	}
+	return nil, nil
+}
+
 func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.BidRequest) (*openrtb2.Bid, []error) {
 
 	creativeWidth, widthErr := strconv.ParseInt(ad.CreativeWidth, 10, 64)
@@ -376,6 +416,13 @@ func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.
 		}
 	}
 
+	extJson, err := generateReturnExt(ad, request)
+	if err != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Error extracting Ext: %s", err.Error()),
+		}}
+	}
+
 	adDomain := []string{}
 	for _, url := range ad.DestinationUrls {
 		domainArray := strings.Split(url, "/")
@@ -395,6 +442,7 @@ func generateAdResponse(ad Ad, imp openrtb2.Imp, html string, request *openrtb2.
 		Price:   price * 1000,
 		AdM:     html,
 		ADomain: adDomain,
+		Ext:     extJson,
 	}
 	return &bid, nil
 
diff --git a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json
index 3a50789e4dd..2472bce3c1c 100644
--- a/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json
+++ b/adapters/adnuntius/adnuntiustest/exemplary/simple-banner.json
@@ -31,7 +31,7 @@
     "httpCalls": [
         {
             "expectedRequest": {
-                "uri": "http://whatever.url?format=json&tzo=0",
+                "uri": "http://whatever.url?format=prebid&tzo=0",
                 "body": {
                     "adUnits": [
                         {
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json
index 2565fee93c9..caaf8892388 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dealId.json
@@ -31,7 +31,7 @@
   "httpCalls": [
       {
           "expectedRequest": {
-              "uri": "http://whatever.url?format=json&tzo=0",
+              "uri": "http://whatever.url?format=prebid&tzo=0",
               "body": {
                   "adUnits": [
                       {
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json
new file mode 100644
index 00000000000..0b44aa16dca
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName-omitted.json
@@ -0,0 +1,133 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"site": {
+			"ext":{
+				"data" : {
+					"key": ["value"]
+				}
+			}
+		},
+		"regs": {
+			"ext": {
+				"dsa": {
+					"dsarequired": 3,
+					"datatopub": 1
+				}
+			}
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=prebid&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"kv": {
+						"key": ["value"]
+					},
+					"metaData": {
+						"usi": "1kjh3429kjh295jkl"
+					},
+					"context": "unknown"
+				},
+				"impIDs":["test-imp-id"]
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv",
+									"advertiser": {
+										"name": "Name"
+									}
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240,
+						"ext": {
+							"dsa": {
+								"paid": "Name",
+								"behalf": "Name",
+								"adrender": 0
+							}
+						}
+					},
+					"type": "banner"
+					
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json
new file mode 100644
index 00000000000..7999bd476aa
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-legalName.json
@@ -0,0 +1,134 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"site": {
+			"ext":{
+				"data" : {
+					"key": ["value"]
+				}
+			}
+		},
+		"regs": {
+			"ext": {
+				"dsa": {
+					"dsarequired": 3,
+					"datatopub": 1
+				}
+			}
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=prebid&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"kv": {
+						"key": ["value"]
+					},
+					"metaData": {
+						"usi": "1kjh3429kjh295jkl"
+					},
+					"context": "unknown"
+				},
+				"impIDs":["test-imp-id"]
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv",
+									"advertiser": {
+										"name": "Name",
+										"legalName": "LegalName"
+									}
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240,
+						"ext": {
+							"dsa": {
+								"paid": "LegalName",
+								"behalf": "LegalName",
+								"adrender": 0
+							}
+						}
+					},
+					"type": "banner"
+					
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json
new file mode 100644
index 00000000000..11cea9bcf66
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-dsa-advertiser-omitted.json
@@ -0,0 +1,123 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"site": {
+			"ext":{
+				"data" : {
+					"key": ["value"]
+				}
+			}
+		},
+		"regs": {
+			"ext": {
+				"dsa": {
+					"dsarequired": 3,
+					"datatopub": 1
+				}
+			}
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=prebid&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"kv": {
+						"key": ["value"]
+					},
+					"metaData": {
+						"usi": "1kjh3429kjh295jkl"
+					},
+					"context": "unknown"
+				},
+				"impIDs":["test-imp-id"]
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv"
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240
+					},
+					"type": "banner"
+					
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
\ No newline at end of file
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
index c73d69bad83..3e72810422c 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gdpr.json
@@ -38,7 +38,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://gdpr.url?consentString=CONSENT_STRING&format=json&gdpr=1&tzo=0",
+				"uri": "http://gdpr.url?consentString=CONSENT_STRING&format=prebid&gdpr=1&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json
index d6301fe71cf..2078258cab7 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-gross-bids.json
@@ -31,7 +31,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json
index ebb25b2b7ad..35c9bca909c 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-net-bids.json
@@ -31,7 +31,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json
index b0d74565771..dfec207bb24 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies-parameter.json
@@ -31,7 +31,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&noCookies=true&tzo=0",
+				"uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json
index f1ddd3f7d5a..b8598b3aa90 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-noCookies.json
@@ -35,7 +35,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&noCookies=true&tzo=0",
+				"uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json
index d6f292d8cd5..7eb3d8afdb2 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-order-multi-imp.json
@@ -47,11 +47,11 @@
 			}
 		]
 	},
-	
+
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
@@ -158,6 +158,6 @@
 			],
 			"currency": "NOK"
 		}
-	
+
     ]
 }
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json
index 4b8e6de346e..99d23f2d3fc 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/check-userId.json
@@ -30,7 +30,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json
index f3aebd99621..904345297ac 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs-ext.json
@@ -33,7 +33,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json
index 06593630c43..5c88e055789 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/empty-regs.json
@@ -32,7 +32,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
index 1987fb9d08e..48670ee1e03 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/height-error.json
@@ -30,7 +30,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json
new file mode 100644
index 00000000000..bf0365f60a9
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/invalid-regs-ext.json
@@ -0,0 +1,46 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"site": {
+			"ext":{
+				"data" : {
+					"key": ["value"]
+				}
+			}
+		},
+		"regs": {
+			"ext": ""
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"expectedMakeRequestsErrors": [
+		{
+			"value": "failed to parse URL: [failed to parse Adnuntius endpoint: failed to parse ExtRegs in Adnuntius GDPR check: json: cannot unmarshal string into Go value of type openrtb_ext.ExtRegs]",
+			"comparison": "literal"
+		}
+	]
+}
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json
index 1d4c5bf0747..691de79c25f 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/max-deals-test.json
@@ -28,11 +28,11 @@
 			}
 		]
 	},
-	
+
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
index bcfecfa8e98..789a7f5d901 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/send-header-information.json
@@ -50,7 +50,7 @@
 						"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Mobile Safari/537.36"
 					]
 				},
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json
new file mode 100644
index 00000000000..ddf37962054
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/site-ext.json
@@ -0,0 +1,114 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"id": "1kjh3429kjh295jkl"
+		},
+		"site": {
+			"ext":{
+				"data" : {
+					"key": ["value"]
+				}
+			}
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=prebid&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"kv": {
+						"key": ["value"]
+					},
+					"metaData": {
+						"usi": "1kjh3429kjh295jkl"
+					},
+					"context": "unknown"
+				},
+				"impIDs":["test-imp-id"]
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv"
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240
+					},
+					"type": "banner"
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/size-check.json b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json
index c05428c123f..ca551380d4b 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/size-check.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/size-check.json
@@ -28,7 +28,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&noCookies=true&tzo=0",
+				"uri": "http://whatever.url?format=prebid&noCookies=true&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json
index f8407b1de5b..84d51bd64f6 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/status-400.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/status-400.json
@@ -27,7 +27,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json
index 2e0f0afcbbd..0cb49b946c1 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/test-networks.json
@@ -32,7 +32,7 @@
 	"httpCalls": [
 			{
 					"expectedRequest": {
-							"uri": "http://whatever.url?format=json&tzo=0",
+							"uri": "http://whatever.url?format=prebid&tzo=0",
 							"body": {
 									"adUnits": [
 											{
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json b/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json
new file mode 100644
index 00000000000..2c2dcac1575
--- /dev/null
+++ b/adapters/adnuntius/adnuntiustest/supplemental/user-ext.json
@@ -0,0 +1,113 @@
+{
+	"mockBidRequest": {
+		"id": "test-request-id",
+		"user": {
+			"ext":{
+				"eids" : [
+					{
+						"source": "idProvider",
+						"uids": [
+							{ "id": "userId", "atype": 1, "ext": { "stype": "ppuid" } }
+						]
+					}
+				]
+			}
+		},
+		"imp": [
+			{
+				"id": "test-imp-id",
+				"banner": {
+					"format": [
+						{
+							"w": 300,
+							"h": 250
+						},
+						{
+							"w": 300,
+							"h": 600
+						}
+					]
+				},
+				"ext": {
+					"bidder": {
+						"auId": "123"
+					}
+				}
+			}
+		]
+	},
+	"httpCalls": [
+		{
+			"expectedRequest": {
+				"uri": "http://whatever.url?format=prebid&tzo=0",
+				"body": {
+					"adUnits": [
+						{
+							"auId": "123",
+							"targetId": "123-test-imp-id",
+							"dimensions": [[300,250],[300,600]]
+						}
+					],
+					"metaData": {
+						"usi": "userId"
+					},
+					"context": "unknown"
+				},
+				"impIDs":["test-imp-id"]
+			},
+			"mockResponse": {
+				"status": 200,
+				"body": {
+					"adUnits": [
+						{
+							"auId": "0000000000000123",
+							"targetId": "123-test-imp-id",
+							"html": "<ADCODE>",
+							"responseId": "adn-rsp-900646517",
+							"ads": [
+								{
+									"destinationUrls": {
+										"url": "http://www.google.com"
+									},
+									"bid": {
+										"amount": 20.0,
+										"currency": "NOK"
+									},
+									"adId": "adn-id-1559784094",
+									"creativeWidth": "980",
+									"creativeHeight": "240",
+									"creativeId": "jn9hpzvlsf8cpdmm",
+									"lineItemId": "q7y9qm5b0xt9htrv"
+								}
+							]
+						}
+					]
+				}
+			}
+		}
+	],
+	"expectedBidResponses": [
+		{
+			"bids": [
+				{
+					"bid": {
+						"id": "adn-id-1559784094",
+						"impid": "test-imp-id",
+						"price": 20000,
+						"adm": "<ADCODE>",
+						"adid": "adn-id-1559784094",
+						"adomain": [
+							"google.com"
+						],
+						"cid": "q7y9qm5b0xt9htrv",
+						"crid": "jn9hpzvlsf8cpdmm",
+						"w": 980,
+						"h": 240
+					},
+					"type": "banner"
+				}
+			],
+			"currency": "NOK"
+		}
+	]
+}
diff --git a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
index 4f109942b91..b3d2ecb7efc 100644
--- a/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
+++ b/adapters/adnuntius/adnuntiustest/supplemental/width-error.json
@@ -30,7 +30,7 @@
 	"httpCalls": [
 		{
 			"expectedRequest": {
-				"uri": "http://whatever.url?format=json&tzo=0",
+				"uri": "http://whatever.url?format=prebid&tzo=0",
 				"body": {
 					"adUnits": [
 						{
diff --git a/adapters/adtonos/adtonos.go b/adapters/adtonos/adtonos.go
new file mode 100644
index 00000000000..dff60733955
--- /dev/null
+++ b/adapters/adtonos/adtonos.go
@@ -0,0 +1,142 @@
+package adtonos
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/macros"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpointTemplate *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{
+		endpointTemplate: template,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Invalid imp.ext for impression index %d. Error Infomation: %s", 0, err.Error()),
+		}}
+	}
+	var impExt openrtb_ext.ImpExtAdTonos
+	if err := json.Unmarshal(bidderExt.Bidder, &impExt); err != nil {
+		return nil, []error{&errortypes.BadInput{
+			Message: fmt.Sprintf("Invalid imp.ext.bidder for impression index %d. Error Infomation: %s", 0, err.Error()),
+		}}
+	}
+
+	endpoint, err := a.buildEndpointURL(&impExt)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	requestJson, err := json.Marshal(request)
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	requestData := &adapters.RequestData{
+		Method:  "POST",
+		Uri:     endpoint,
+		Body:    requestJson,
+		Headers: headers,
+		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
+	}
+
+	return []*adapters.RequestData{requestData}, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ImpExtAdTonos) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: params.SupplierID}
+	return macros.ResolveMacros(a.endpointTemplate, endpointParams)
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	bidResponse.Currency = response.Cur
+	var errors []error
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getMediaTypeForBid(seatBid.Bid[i], request.Imp)
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, errors
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid, requestImps []openrtb2.Imp) (openrtb_ext.BidType, error) {
+	if bid.MType != 0 {
+		// If present, use explicit markup type annotation from the bidder:
+		switch bid.MType {
+		case openrtb2.MarkupAudio:
+			return openrtb_ext.BidTypeAudio, nil
+		case openrtb2.MarkupVideo:
+			return openrtb_ext.BidTypeVideo, nil
+		case openrtb2.MarkupBanner:
+			return openrtb_ext.BidTypeBanner, nil
+		case openrtb2.MarkupNative:
+			return openrtb_ext.BidTypeNative, nil
+		}
+	}
+	// As a fallback, guess markup type based on requested type - AdTonos is an audio company so we prioritize that.
+	for _, requestImp := range requestImps {
+		if requestImp.ID == bid.ImpID {
+			if requestImp.Audio != nil {
+				return openrtb_ext.BidTypeAudio, nil
+			} else if requestImp.Video != nil {
+				return openrtb_ext.BidTypeVideo, nil
+			} else {
+				return "", &errortypes.BadInput{
+					Message: fmt.Sprintf("Unsupported bidtype for bid: \"%s\"", bid.ImpID),
+				}
+			}
+		}
+	}
+	return "", &errortypes.BadInput{
+		Message: fmt.Sprintf("Failed to find impression: \"%s\"", bid.ImpID),
+	}
+}
diff --git a/adapters/adtonos/adtonos_test.go b/adapters/adtonos/adtonos_test.go
new file mode 100644
index 00000000000..612e71783be
--- /dev/null
+++ b/adapters/adtonos/adtonos_test.go
@@ -0,0 +1,30 @@
+package adtonos
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{
+		Endpoint: "http://exchange.example.com/bid/{{.PublisherID}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "adtonostest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderAdTonos, config.Adapter{
+		Endpoint: "{{Malformed}}"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json b/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json
new file mode 100644
index 00000000000..c9103e37ee4
--- /dev/null
+++ b/adapters/adtonos/adtonostest/exemplary/simple-audio-with-mtype.json
@@ -0,0 +1,115 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "bidfloor": 4.2,
+        "ext": {
+          "bidder": {
+            "supplierId": "777XYZ123"
+          }
+        },
+        "audio": {
+          "mimes": [
+            "audio/mpeg"
+          ]
+        }
+      }
+    ],
+    "test": 1,
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {}
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://exchange.example.com/bid/777XYZ123",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "some-request-id",
+          "tmax": 1000,
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "bidfloor": 4.2,
+              "ext": {
+                "bidder": {
+                  "supplierId": "777XYZ123"
+                }
+              },
+              "audio": {
+                "mimes": [
+                  "audio/mpeg"
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "http://www.example.com",
+            "domain": "www.example.com"
+          },
+          "device": {},
+          "test": 1
+        },
+        "impIDs":["some-impression-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "crid": "some-creative-id",
+                  "adm": "<VAST>TAG</VAST>",
+                  "price": 6.5,
+                  "mtype": 3
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "crid": "some-creative-id",
+            "adm": "<VAST>TAG</VAST>",
+            "price": 6.5,
+            "mtype": 3
+          },
+          "type": "audio"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adtonos/adtonostest/exemplary/simple-audio.json b/adapters/adtonos/adtonostest/exemplary/simple-audio.json
new file mode 100644
index 00000000000..61cac002660
--- /dev/null
+++ b/adapters/adtonos/adtonostest/exemplary/simple-audio.json
@@ -0,0 +1,113 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "bidfloor": 4.2,
+        "ext": {
+          "bidder": {
+            "supplierId": "777XYZ123"
+          }
+        },
+        "audio": {
+          "mimes": [
+            "audio/mpeg"
+          ]
+        }
+      }
+    ],
+    "test": 1,
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {}
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://exchange.example.com/bid/777XYZ123",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "some-request-id",
+          "tmax": 1000,
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "bidfloor": 4.2,
+              "ext": {
+                "bidder": {
+                  "supplierId": "777XYZ123"
+                }
+              },
+              "audio": {
+                "mimes": [
+                  "audio/mpeg"
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "http://www.example.com",
+            "domain": "www.example.com"
+          },
+          "device": {},
+          "test": 1
+        },
+        "impIDs":["some-impression-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "crid": "some-creative-id",
+                  "adm": "<VAST>TAG</VAST>",
+                  "price": 6.5
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "crid": "some-creative-id",
+            "adm": "<VAST>TAG</VAST>",
+            "price": 6.5
+          },
+          "type": "audio"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adtonos/adtonostest/exemplary/simple-video.json b/adapters/adtonos/adtonostest/exemplary/simple-video.json
new file mode 100644
index 00000000000..f00089b4008
--- /dev/null
+++ b/adapters/adtonos/adtonostest/exemplary/simple-video.json
@@ -0,0 +1,113 @@
+{
+  "mockBidRequest": {
+    "id": "video-request-id",
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "bidfloor": 4.2,
+        "ext": {
+          "bidder": {
+            "supplierId": "777XYZ123"
+          }
+        },
+        "video": {
+          "mimes": [
+            "video/mp4"
+          ]
+        }
+      }
+    ],
+    "test": 1,
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {}
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://exchange.example.com/bid/777XYZ123",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "video-request-id",
+          "tmax": 1000,
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "bidfloor": 4.2,
+              "ext": {
+                "bidder": {
+                  "supplierId": "777XYZ123"
+                }
+              },
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "http://www.example.com",
+            "domain": "www.example.com"
+          },
+          "device": {},
+          "test": 1
+        },
+        "impIDs":["some-impression-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "video-request-id",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "crid": "some-creative-id",
+                  "adm": "<VAST>TAG</VAST>",
+                  "price": 6.5
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "crid": "some-creative-id",
+            "adm": "<VAST>TAG</VAST>",
+            "price": 6.5
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json b/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json
new file mode 100644
index 00000000000..6fa20e3d3c3
--- /dev/null
+++ b/adapters/adtonos/adtonostest/supplemental/wrong-impression-mapping.json
@@ -0,0 +1,103 @@
+{
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "correct-impression-id",
+        "bidfloor": 4.2,
+        "ext": {
+          "bidder": {
+            "supplierId": "777XYZ123"
+          }
+        },
+        "audio": {
+          "mimes": [
+            "audio/mpeg"
+          ]
+        }
+      }
+    ],
+    "test": 1,
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "http://www.example.com",
+      "domain": "www.example.com"
+    },
+    "device": {}
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://exchange.example.com/bid/777XYZ123",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "some-request-id",
+          "tmax": 1000,
+          "imp": [
+            {
+              "id": "correct-impression-id",
+              "bidfloor": 4.2,
+              "ext": {
+                "bidder": {
+                  "supplierId": "777XYZ123"
+                }
+              },
+              "audio": {
+                "mimes": [
+                  "audio/mpeg"
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "http://www.example.com",
+            "domain": "www.example.com"
+          },
+          "device": {},
+          "test": 1
+        },
+        "impIDs":["correct-impression-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "unexpected-impression-id",
+                  "crid": "some-creative-id",
+                  "adm": "<VAST>TAG</VAST>",
+                  "price": 6.5
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [{"currency":"USD","bids":[]}],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Failed to find impression: \"unexpected-impression-id\"",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/adtonos/params_test.go b/adapters/adtonos/params_test.go
new file mode 100644
index 00000000000..98fa85f7b0d
--- /dev/null
+++ b/adapters/adtonos/params_test.go
@@ -0,0 +1,43 @@
+package adtonos
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderAdTonos, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderAdTonos, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"supplierId": ""}`,
+	`{"supplierId": "7YZxxxdJMSXWv7SwY"}`,
+}
+
+var invalidParams = []string{
+	`{"supplierId": 42}`,
+}
diff --git a/adapters/bidmatic/bidmatic.go b/adapters/bidmatic/bidmatic.go
new file mode 100644
index 00000000000..950107ea0e0
--- /dev/null
+++ b/adapters/bidmatic/bidmatic.go
@@ -0,0 +1,206 @@
+package bidmatic
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type bidmaticImpExt struct {
+	Bidmatic openrtb_ext.ExtImpBidmatic `json:"bidmatic"`
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	totalImps := len(request.Imp)
+	errors := make([]error, 0, totalImps)
+	imp2source := make(map[int][]int)
+
+	for i := 0; i < totalImps; i++ {
+		sourceId, err := validateImpression(&request.Imp[i])
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		if _, ok := imp2source[sourceId]; !ok {
+			imp2source[sourceId] = make([]int, 0, totalImps-i)
+		}
+
+		imp2source[sourceId] = append(imp2source[sourceId], i)
+	}
+
+	totalReqs := len(imp2source)
+	if totalReqs == 0 {
+		return nil, errors
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	reqs := make([]*adapters.RequestData, 0, totalReqs)
+
+	imps := request.Imp
+	request.Imp = make([]openrtb2.Imp, 0, len(imps))
+	for sourceId, impIds := range imp2source {
+		request.Imp = request.Imp[:0]
+
+		for i := 0; i < len(impIds); i++ {
+			request.Imp = append(request.Imp, imps[impIds[i]])
+		}
+
+		body, err := json.Marshal(request)
+		if err != nil {
+			errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err))
+			return nil, errors
+		}
+
+		reqs = append(reqs, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     a.endpoint + fmt.Sprintf("?source=%d", sourceId),
+			Body:    body,
+			Headers: headers,
+			ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
+		})
+	}
+
+	if len(reqs) == 0 {
+		return nil, errors
+	}
+
+	return reqs, errors
+}
+
+func (a *adapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(httpRes) {
+		return nil, nil
+	}
+	if err := adapters.CheckResponseStatusCodeForErrors(httpRes); err != nil {
+		return nil, []error{err}
+	}
+
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil {
+		return nil, []error{&errortypes.BadServerResponse{
+			Message: fmt.Sprintf("error while decoding response, err: %s", err),
+		}}
+	}
+
+	bidResponse := adapters.NewBidderResponse()
+	var errors []error
+
+	var impOK bool
+	for _, sb := range bidResp.SeatBid {
+		for i := 0; i < len(sb.Bid); i++ {
+
+			bid := sb.Bid[i]
+
+			impOK = false
+			mediaType := openrtb_ext.BidTypeBanner
+			bid.MType = openrtb2.MarkupBanner
+		loop:
+			for _, imp := range bidReq.Imp {
+				if imp.ID == bid.ImpID {
+
+					impOK = true
+
+					switch {
+					case imp.Video != nil:
+						mediaType = openrtb_ext.BidTypeVideo
+						bid.MType = openrtb2.MarkupVideo
+						break loop
+					case imp.Banner != nil:
+						mediaType = openrtb_ext.BidTypeBanner
+						bid.MType = openrtb2.MarkupBanner
+						break loop
+					case imp.Audio != nil:
+						mediaType = openrtb_ext.BidTypeAudio
+						bid.MType = openrtb2.MarkupAudio
+						break loop
+					case imp.Native != nil:
+						mediaType = openrtb_ext.BidTypeNative
+						bid.MType = openrtb2.MarkupNative
+						break loop
+					}
+				}
+			}
+
+			if !impOK {
+				errors = append(errors, &errortypes.BadServerResponse{
+					Message: fmt.Sprintf("ignoring bid id=%s, request doesn't contain any impression with id=%s", bid.ID, bid.ImpID),
+				})
+				continue
+			}
+
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &bid,
+				BidType: mediaType,
+			})
+		}
+	}
+
+	return bidResponse, errors
+}
+
+func validateImpression(imp *openrtb2.Imp) (int, error) {
+	if len(imp.Ext) == 0 {
+		return 0, &errortypes.BadInput{
+			Message: fmt.Sprintf("ignoring imp id=%s, extImpBidder is empty", imp.ID),
+		}
+	}
+
+	var bidderExt adapters.ExtImpBidder
+
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return 0, &errortypes.BadInput{
+			Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err),
+		}
+	}
+
+	impExt := openrtb_ext.ExtImpBidmatic{}
+	err := json.Unmarshal(bidderExt.Bidder, &impExt)
+	if err != nil {
+		return 0, &errortypes.BadInput{
+			Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err),
+		}
+	}
+
+	// common extension for all impressions
+	var impExtBuffer []byte
+
+	impExtBuffer, err = json.Marshal(&bidmaticImpExt{
+		Bidmatic: impExt,
+	})
+	if err != nil {
+		return 0, &errortypes.BadInput{
+			Message: fmt.Sprintf("ignoring imp id=%s, error while marshaling impExt, err: %s", imp.ID, err),
+		}
+	}
+
+	if impExt.BidFloor > 0 {
+		imp.BidFloor = impExt.BidFloor
+	}
+
+	imp.Ext = impExtBuffer
+
+	source, err := impExt.SourceId.Int64() // json.Unmarshal returns err if it isn't valid
+	if err != nil {
+		return 0, err
+	}
+	return int(source), nil
+}
+
+// Builder builds a new instance of the bidmatic adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	return &adapter{endpoint: config.Endpoint}, nil
+}
diff --git a/adapters/bidmatic/bidmatic_test.go b/adapters/bidmatic/bidmatic_test.go
new file mode 100644
index 00000000000..c6a31823223
--- /dev/null
+++ b/adapters/bidmatic/bidmatic_test.go
@@ -0,0 +1,23 @@
+package bidmatic
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(
+		openrtb_ext.BidderBidmatic,
+		config.Adapter{Endpoint: "http://adapter.bidmatic.io/pbs/ortb"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"},
+	)
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "bidmatictest", bidder)
+}
diff --git a/adapters/bidmatic/bidmatictest/exemplary/media-type-mapping.json b/adapters/bidmatic/bidmatictest/exemplary/media-type-mapping.json
new file mode 100644
index 00000000000..57f1215af43
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/exemplary/media-type-mapping.json
@@ -0,0 +1,91 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidmatic": {
+                  "source": 1000
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-bid-id",
+                  "impid": "test-imp-id",
+                  "mtype": 2,
+                  "price": 3.5,
+                  "w": 900,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-bid-id",
+            "impid": "test-imp-id",
+            "mtype": 2,
+            "price": 3.5,
+            "w": 900,
+            "h": 250
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/bidmatic/bidmatictest/exemplary/simple-banner.json b/adapters/bidmatic/bidmatictest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..d3c41278231
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/exemplary/simple-banner.json
@@ -0,0 +1,98 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000,
+            "siteId": 1234,
+            "bidFloor": 20
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "banner": {
+                "format": [
+                  {"w":300,"h":250},
+                  {"w":300,"h":600}
+                ]
+              },
+              "bidfloor": 20,
+              "ext": {
+                "bidmatic": {
+                  "source": 1000,
+                  "siteId": 1234,
+                  "bidFloor": 20
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-bid-id",
+                  "impid": "test-imp-id",
+                  "mtype": 2,
+                  "price": 3.5,
+                  "w": 900,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "test-bid-id",
+            "impid": "test-imp-id",
+            "mtype": 1,
+            "price": 3.5,
+            "w": 900,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/bidmatic/bidmatictest/exemplary/simple-video.json b/adapters/bidmatic/bidmatictest/exemplary/simple-video.json
new file mode 100644
index 00000000000..a9bcb6a141e
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/exemplary/simple-video.json
@@ -0,0 +1,57 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidmatic": {
+                  "source": 1000
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/bidmatic/bidmatictest/supplemental/explicit-dimensions.json b/adapters/bidmatic/bidmatictest/supplemental/explicit-dimensions.json
new file mode 100644
index 00000000000..b1f2f6ea510
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/supplemental/explicit-dimensions.json
@@ -0,0 +1,60 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ],
+          "w": 100,
+          "h": 400
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "w": 100,
+                "h": 400
+              },
+              "ext": {
+                "bidmatic": {
+                 "source": 1000
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 204
+      }
+    }
+  ],
+  "expectedBidResponses": []
+}
diff --git a/adapters/bidmatic/bidmatictest/supplemental/imp-ext-empty.json b/adapters/bidmatic/bidmatictest/supplemental/imp-ext-empty.json
new file mode 100644
index 00000000000..0607da05fb9
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/supplemental/imp-ext-empty.json
@@ -0,0 +1,21 @@
+{
+  "mockBidRequest": {
+    "id": "unsupported-native-request",
+    "imp": [
+      {
+        "id": "unsupported-native-imp",
+        "video": {
+          "w": 100,
+          "h": 200
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "ignoring imp id=unsupported-native-imp, extImpBidder is empty",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-ext.json b/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-ext.json
new file mode 100644
index 00000000000..8154afed75f
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-ext.json
@@ -0,0 +1,26 @@
+{
+  "mockBidRequest": {
+    "id": "unsupported-native-request",
+    "imp": [
+      {
+        "id": "unsupported-native-imp",
+        "video": {
+         "w": 100,
+          "h": 200
+        },
+        "ext": {
+          "bidder": {
+            "source": "some string instead of int"
+          }
+        }
+      }
+    ]
+  },
+
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "ignoring imp id=unsupported-native-imp, error while decoding impExt, err: json: invalid number literal, trying to unmarshal \"\\\"some string instead of int\\\"\" into Number",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-mapping.json b/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-mapping.json
new file mode 100644
index 00000000000..05679082aa3
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/supplemental/wrong-impression-mapping.json
@@ -0,0 +1,79 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "video": {
+          "w": 900,
+          "h": 250,
+          "mimes": [
+            "video/x-flv",
+            "video/mp4"
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000
+          }
+        }
+      }
+    ]
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id":"test-imp-id",
+              "video": {
+                "w": 900,
+                "h": 250,
+                "mimes": [
+                  "video/x-flv",
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "bidmatic": {
+                  "source": 1000
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "test-bid-id",
+                  "impid": "SOME-WRONG-IMP-ID",
+                  "price": 3.5,
+                  "w": 900,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [{"currency":"USD","bids":[]}],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "ignoring bid id=test-bid-id, request doesn't contain any impression with id=SOME-WRONG-IMP-ID",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidmatic/bidmatictest/supplemental/wrong-response.json b/adapters/bidmatic/bidmatictest/supplemental/wrong-response.json
new file mode 100644
index 00000000000..ad09b32cd1a
--- /dev/null
+++ b/adapters/bidmatic/bidmatictest/supplemental/wrong-response.json
@@ -0,0 +1,65 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ],
+          "w": 100,
+          "h": 400
+        },
+        "ext": {
+          "bidder": {
+            "source": 1000
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://adapter.bidmatic.io/pbs/ortb?source=1000",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ],
+                "w": 100,
+                "h": 400
+              },
+              "ext": {
+                "bidmatic": {
+                  "source": 1000
+                }
+              }
+            }
+          ]
+        },
+        "impIDs":["test-imp-id"]
+      },
+      "mockResponse": {
+        "status": 200
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "error while decoding response, err: unexpected end of JSON input",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/bidmatic/params_test.go b/adapters/bidmatic/params_test.go
new file mode 100644
index 00000000000..6bdc5f4339d
--- /dev/null
+++ b/adapters/bidmatic/params_test.go
@@ -0,0 +1,64 @@
+package bidmatic
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/bidmatic.json
+// These also validate the format of the external API: request.imp[i].ext.prebid.bidder.bidmatic
+// TestValidParams makes sure that the bidmatic schema accepts all imp.ext fields which we intend to support.
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, validParam := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmatic, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected bidmatic params: %s", validParam)
+		}
+	}
+}
+
+// TestInvalidParams makes sure that the bidmatic schema rejects all the imp.ext fields we don't support.
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderBidmatic, json.RawMessage(invalidParam)); err == nil {
+			ext := openrtb_ext.ExtImpBidmatic{}
+			err = json.Unmarshal([]byte(invalidParam), &ext)
+			if err == nil {
+				t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+			}
+		}
+	}
+}
+
+var validParams = []string{
+	`{"source":123}`,
+	`{"source":"123"}`,
+	`{"source":123,"placementId":1234}`,
+	`{"source":123,"siteId":4321}`,
+	`{"source":"123","siteId":0,"bidFloor":0}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"source":"qwerty"}`,
+	`{"source":"123","placementId":"123"}`,
+	`{"source":123, "placementId":"123", "siteId":"321"}`,
+}
diff --git a/adapters/bluesea/blueseatest/exemplary/site-banner.json b/adapters/bluesea/blueseatest/exemplary/site-banner.json
new file mode 100644
index 00000000000..b3ca13f8d53
--- /dev/null
+++ b/adapters/bluesea/blueseatest/exemplary/site-banner.json
@@ -0,0 +1,133 @@
+{
+    "mockBidRequest":{
+        "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+        "site":{
+            "id": "100",
+            "domain": "test.domain",
+            "page": "https://test.domain?target=_blank",
+            "keywords": "fashion"
+        },
+        "imp":[
+            {
+                "id":"1",
+                "banner":{
+                    "w":300,
+                    "h":250
+                },
+                "secure":1,
+                "ext":{
+                    "bidder":{
+                        "pubid":"test-pubid",
+                        "token":"test-pub-token"
+                    }
+                }
+            }
+        ],
+        "device":{
+            "os":"android",
+            "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+            "ip":"101.101.101.101",
+            "h":1280,
+            "w":720
+        },
+        "at":1,
+        "tmax":1200,
+        "test":1
+    },
+    "httpCalls":[
+        {
+            "expectedRequest":{
+                "uri":"https://test.prebid.bluesea?pubid=test-pubid&token=test-pub-token",
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "site":{
+                        "id":"100",
+                        "domain": "test.domain",
+                        "page": "https://test.domain?target=_blank",
+                        "keywords": "fashion"
+                    },
+                    "imp":[
+                        {
+                            "id":"1",
+                            "banner":{
+                                "w":300,
+                                "h":250
+                            },
+                            "secure":1,
+                            "ext":{
+                                "bidder":{
+                                    "pubid":"test-pubid",
+                                    "token":"test-pub-token"
+                                }
+                            }
+                        }
+                    ],
+                    "device":{
+                        "os":"android",
+                        "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+                        "ip":"101.101.101.101",
+                        "h":1280,
+                        "w":720
+                    },
+                    "at":1,
+                    "tmax":1200,
+                    "test":1
+                },
+                "impIDs":["1"]
+            },
+            "mockResponse":{
+                "status":200,
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "seatbid":[
+                        {
+                            "bid":[
+                                {
+                                    "price":0.01,
+                                    "adm":"test-adm",
+                                    "impid":"1",
+                                    "id":"test-bid-id",
+                                    "h":250,
+                                    "adomain":[
+                                        "adv.com"
+                                    ],
+                                    "crid":"test-site-crid",
+                                    "w":300,
+                                    "ext":{
+                                        "mediatype":"banner"
+                                    }
+                                }
+                            ],
+                            "seat":"test-seat"
+                        }
+                    ],
+                    "cur":"USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses":[
+        {
+            "bids":[
+                {
+                    "bid":{
+                        "price":0.01,
+                        "adm":"test-adm",
+                        "impid":"1",
+                        "id":"test-bid-id",
+                        "h":250,
+                        "adomain":[
+                            "adv.com"
+                        ],
+                        "crid":"test-site-crid",
+                        "w":300,
+                         "ext":{
+                             "mediatype":"banner"
+                         }
+                    },
+                    "type":"banner"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/bluesea/blueseatest/exemplary/site-native.json b/adapters/bluesea/blueseatest/exemplary/site-native.json
new file mode 100644
index 00000000000..b907ebdbea4
--- /dev/null
+++ b/adapters/bluesea/blueseatest/exemplary/site-native.json
@@ -0,0 +1,133 @@
+{
+    "mockBidRequest":{
+        "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+        "site":{
+            "id": "100",
+            "domain": "test.domain",
+            "page": "https://test.domain?target=_blank",
+            "keywords": "fashion"
+        },
+        "imp":[
+            {
+                "id":"1",
+                "native":{
+                    "request":"{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":150}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":100,\"hmin\":100}},{\"id\":3,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}",
+                    "ver":"1.2"
+                },
+                "secure":1,
+                "ext":{
+                    "bidder":{
+                        "pubid":"test-pubid",
+                        "token":"test-pub-token"
+                    }
+                }
+            }
+        ],
+        "device":{
+            "os":"android",
+            "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+            "ip":"101.101.101.101",
+            "h":1280,
+            "w":720
+        },
+        "at":1,
+        "tmax":1200,
+        "test":1
+    },
+    "httpCalls":[
+        {
+            "expectedRequest":{
+                "uri":"https://test.prebid.bluesea?pubid=test-pubid&token=test-pub-token",
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "site":{
+                        "id":"100",
+                        "domain": "test.domain",
+                        "page": "https://test.domain?target=_blank",
+                        "keywords": "fashion"
+                    },
+                    "imp":[
+                        {
+                            "id":"1",
+                            "native":{
+                                "request":"{\"ver\":\"1.2\",\"context\":1,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":150}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":100,\"hmin\":100}},{\"id\":3,\"required\":1,\"data\":{\"type\":2,\"len\":120}}]}",
+                                "ver":"1.2"
+                            },
+                            "secure":1,
+                            "ext":{
+                                "bidder":{
+                                    "pubid":"test-pubid",
+                                    "token":"test-pub-token"
+                                }
+                            }
+                        }
+                    ],
+                    "device":{
+                        "os":"android",
+                        "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+                        "ip":"101.101.101.101",
+                        "h":1280,
+                        "w":720
+                    },
+                    "at":1,
+                    "tmax":1200,
+                    "test":1
+                },
+                "impIDs":["1"]
+            },
+            "mockResponse":{
+                "status":200,
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "seatbid":[
+                        {
+                            "bid":[
+                                {
+                                    "price":0.01,
+                                    "adm":"test-native-adm",
+                                    "impid":"1",
+                                    "id":"test-bid-id",
+                                    "h":250,
+                                    "adomain":[
+                                        "adv.com"
+                                    ],
+                                    "crid":"test-site-native-crid",
+                                    "w":300,
+                                    "ext":{
+                                        "mediatype":"native"
+                                    }
+                                }
+                            ],
+                            "seat":"test-seat"
+                        }
+                    ],
+                    "cur":"USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses":[
+        {
+            "bids":[
+                {
+                    "bid":{
+                        "price":0.01,
+                        "adm":"test-native-adm",
+                        "impid":"1",
+                        "id":"test-bid-id",
+                        "h":250,
+                        "adomain":[
+                            "adv.com"
+                        ],
+                        "crid":"test-site-native-crid",
+                        "w":300,
+                        "ext":{
+                            "mediatype":"native"
+                        }
+                    },
+                    "type":"native"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/bluesea/blueseatest/exemplary/site-video.json b/adapters/bluesea/blueseatest/exemplary/site-video.json
new file mode 100644
index 00000000000..dd191105cd3
--- /dev/null
+++ b/adapters/bluesea/blueseatest/exemplary/site-video.json
@@ -0,0 +1,163 @@
+{
+    "mockBidRequest":{
+        "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+        "site":{
+            "id": "100",
+            "domain": "test.domain",
+            "page": "https://test.domain?target=_blank",
+            "keywords": "fashion"
+        },
+        "imp":[
+            {
+                "id":"1",
+                "video":{
+                    "mimes":[
+                        "video/mp4",
+                        "application/javascript",
+                        "video/webm"
+                    ],
+                    "minduration":5,
+                    "maxduration":120,
+                    "protocols":[
+                        2,
+                        3,
+                        5,
+                        6
+                    ],
+                    "pos":7,
+                    "w":320,
+                    "h":480,
+                    "linearity":1
+                },
+                "secure":1,
+                "ext":{
+                    "bidder":{
+                        "pubid":"test-pubid",
+                        "token":"test-pub-token"
+                    }
+                }
+            }
+        ],
+        "device":{
+            "os":"android",
+            "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+            "ip":"101.101.101.101",
+            "h":1280,
+            "w":720
+        },
+        "at":1,
+        "tmax":1200,
+        "test":1
+    },
+    "httpCalls":[
+        {
+            "expectedRequest":{
+                "uri":"https://test.prebid.bluesea?pubid=test-pubid&token=test-pub-token",
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "site":{
+                        "id":"100",
+                        "domain": "test.domain",
+                        "page": "https://test.domain?target=_blank",
+                        "keywords": "fashion"
+                    },
+                    "imp":[
+                        {
+                            "id":"1",
+                            "video":{
+                                "mimes":[
+                                    "video/mp4",
+                                    "application/javascript",
+                                    "video/webm"
+                                ],
+                                "minduration":5,
+                                "maxduration":120,
+                                "protocols":[
+                                    2,
+                                    3,
+                                    5,
+                                    6
+                                ],
+                                "pos":7,
+                                "w":320,
+                                "h":480,
+                                "linearity":1
+                            },
+                            "secure":1,
+                            "ext":{
+                                "bidder":{
+                                    "pubid":"test-pubid",
+                                    "token":"test-pub-token"
+                                }
+                            }
+                        }
+                    ],
+                    "device":{
+                        "os":"android",
+                        "ua":"Mozilla/5.0 (Linux; Android 8.0.0; SC-04J Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/113.0.5672.162 Mobile Safari/537.36",
+                        "ip":"101.101.101.101",
+                        "h":1280,
+                        "w":720
+                    },
+                    "at":1,
+                    "tmax":1200,
+                    "test":1
+                },
+                "impIDs":["1"]
+            },
+            "mockResponse":{
+                "status":200,
+                "body":{
+                    "id":"b655d86c-fdf6-4e68-a1e9-abc223f84a65",
+                    "seatbid":[
+                        {
+                            "bid":[
+                                {
+                                    "price":0.01,
+                                    "adm":"test-vast",
+                                    "impid":"1",
+                                    "id":"test-bid-id",
+                                    "h":480,
+                                    "adomain":[
+                                        "adv.com"
+                                    ],
+                                    "crid":"test-site-crid",
+                                    "w":320,
+                                    "ext":{
+                                        "mediatype":"video"
+                                    }
+                                }
+                            ],
+                            "seat":"test-seat"
+                        }
+                    ],
+                    "cur":"USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses":[
+        {
+            "bids":[
+                {
+                    "bid":{
+                        "price":0.01,
+                        "adm":"test-vast",
+                        "impid":"1",
+                        "id":"test-bid-id",
+                        "h":480,
+                        "adomain":[
+                            "adv.com"
+                        ],
+                        "crid":"test-site-crid",
+                        "w":320,
+                        "ext":{
+                            "mediatype":"video"
+                        }
+                    },
+                    "type":"video"
+                }
+            ]
+        }
+    ]
+}
diff --git a/adapters/connectad/connectad.go b/adapters/connectad/connectad.go
index 5bf60106016..1617d3d66fd 100644
--- a/adapters/connectad/connectad.go
+++ b/adapters/connectad/connectad.go
@@ -138,7 +138,7 @@ func preprocess(request *openrtb2.BidRequest) []error {
 }
 
 func addImpInfo(imp *openrtb2.Imp, secure *int8, cadExt *openrtb_ext.ExtImpConnectAd) {
-	imp.TagID = strconv.Itoa(cadExt.SiteID)
+	imp.TagID = strconv.Itoa(int(cadExt.SiteID))
 	imp.Secure = secure
 
 	if cadExt.Bidfloor != 0 {
diff --git a/adapters/copper6ssp/copper6ssp.go b/adapters/copper6ssp/copper6ssp.go
new file mode 100644
index 00000000000..57ec6bcc17b
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssp.go
@@ -0,0 +1,157 @@
+package copper6ssp
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type reqBodyExt struct {
+	Copper6sspBidderExt reqBodyExtBidder `json:"bidder"`
+}
+
+type reqBodyExtBidder struct {
+	Type        string `json:"type"`
+	PlacementID string `json:"placementId,omitempty"`
+	EndpointID  string `json:"endpointId,omitempty"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+	var adapterRequests []*adapters.RequestData
+
+	reqCopy := *request
+	for _, imp := range request.Imp {
+		reqCopy.Imp = []openrtb2.Imp{imp}
+
+		var bidderExt adapters.ExtImpBidder
+		var copper6sspExt openrtb_ext.ImpExtCopper6ssp
+
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if err := json.Unmarshal(bidderExt.Bidder, &copper6sspExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		impExt := reqBodyExt{Copper6sspBidderExt: reqBodyExtBidder{}}
+
+		if copper6sspExt.PlacementID != "" {
+			impExt.Copper6sspBidderExt.PlacementID = copper6sspExt.PlacementID
+			impExt.Copper6sspBidderExt.Type = "publisher"
+		} else if copper6sspExt.EndpointID != "" {
+			impExt.Copper6sspBidderExt.EndpointID = copper6sspExt.EndpointID
+			impExt.Copper6sspBidderExt.Type = "network"
+		}
+
+		finalImpExt, err := json.Marshal(impExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		reqCopy.Imp[0].Ext = finalImpExt
+
+		adapterReq, err := a.makeRequest(&reqCopy)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		if adapterReq != nil {
+			adapterRequests = append(adapterRequests, adapterReq)
+		}
+	}
+
+	if len(adapterRequests) == 0 {
+		return nil, errs
+	}
+
+	return adapterRequests, nil
+}
+
+func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJSON,
+		Headers: headers,
+		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
+	}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	if len(response.Cur) != 0 {
+		bidResponse.Currency = response.Cur
+	}
+
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getBidType(seatBid.Bid[i])
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
+
+func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	// determinate media type by bid response field mtype
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	}
+
+	return "", fmt.Errorf("could not define media type for impression: %s", bid.ImpID)
+}
diff --git a/adapters/copper6ssp/copper6ssp_test.go b/adapters/copper6ssp/copper6ssp_test.go
new file mode 100644
index 00000000000..25bd2dbc67a
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssp_test.go
@@ -0,0 +1,20 @@
+package copper6ssp
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderCopper6ssp, config.Adapter{
+		Endpoint: "https://example.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "copper6ssptest", bidder)
+}
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/endpointId.json b/adapters/copper6ssp/copper6ssptest/exemplary/endpointId.json
new file mode 100644
index 00000000000..3dc82f836da
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/endpointId.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/multi-format.json b/adapters/copper6ssp/copper6ssptest/exemplary/multi-format.json
new file mode 100644
index 00000000000..9e897a9a1bb
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/multi-format.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/multi-imp.json b/adapters/copper6ssp/copper6ssptest/exemplary/multi-imp.json
new file mode 100644
index 00000000000..63828352744
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/multi-imp.json
@@ -0,0 +1,253 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id2",
+                "tagid": "test2",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 3000,
+                            "h": 2500
+                        },
+                        {
+                            "w": 3000,
+                            "h": 6000
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test2"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        },
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id2",
+                            "tagid": "test2",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 3000,
+                                        "h": 2500
+                                    },
+                                    {
+                                        "w": 3000,
+                                        "h": 6000
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test2",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id2"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id2",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 3000,
+                                    "h": 2500,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        },
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id2",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 3000,
+                        "h": 2500,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/simple-banner.json b/adapters/copper6ssp/copper6ssptest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..3bff225709d
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/simple-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/simple-native.json b/adapters/copper6ssp/copper6ssptest/exemplary/simple-native.json
new file mode 100644
index 00000000000..63b4a5c824a
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/simple-native.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "native": {
+                    "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                    "ver": "1.1"
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "native": {
+                                "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                                "ver": "1.1"
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 4,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "native"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 4,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "native"
+                            }
+                        }
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/simple-video.json b/adapters/copper6ssp/copper6ssptest/exemplary/simple-video.json
new file mode 100644
index 00000000000..60156082dc7
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/simple-video.json
@@ -0,0 +1,131 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    },
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 2,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "video"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 2,
+                        "ext": {
+                            "prebid": {
+                                "type": "video"
+                            }
+                        }
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/exemplary/simple-web-banner.json b/adapters/copper6ssp/copper6ssptest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000000..3ff97037a82
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/exemplary/simple-web-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "id": "1",
+            "domain": "test.com"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "Ubuntu"
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "id": "1",
+                        "domain": "test.com"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "Ubuntu"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 468,
+                                    "h": 60,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "copper6ssp"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 468,
+                        "h": 60,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/bad_media_type.json b/adapters/copper6ssp/copper6ssptest/supplemental/bad_media_type.json
new file mode 100644
index 00000000000..3b61edd137d
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/bad_media_type.json
@@ -0,0 +1,83 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://example.com",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": {
+                "id": "test-request-id",
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "test_bid_id",
+                                "impid": "test-imp-id",
+                                "price": 0.27543,
+                                "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://example.com&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                "cid": "test_cid",
+                                "crid": "test_crid",
+                                "dealid": "test_dealid",
+                                "w": 300,
+                                "h": 250,
+                                "ext": {}
+                            }
+                        ],
+                        "seat": "copper6ssp"
+                    }
+                ],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "could not define media type for impression: test-imp-id",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/bad_response.json b/adapters/copper6ssp/copper6ssptest/supplemental/bad_response.json
new file mode 100644
index 00000000000..cd4169c9974
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/bad_response.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://example.com",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": ""
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-bidder-param.json b/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-bidder-param.json
new file mode 100644
index 00000000000..fbe564b6a26
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-bidder-param.json
@@ -0,0 +1,42 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": []
+                    }
+                }
+            }
+        ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "json: cannot unmarshal array into Go struct field ImpExtCopper6ssp.endpointId of type string",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-imp-ext.json b/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-imp-ext.json
new file mode 100644
index 00000000000..9d6710efe37
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/no-valid-imp-ext.json
@@ -0,0 +1,38 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": "invalid"
+            }
+        ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "json: cannot unmarshal string into Go value of type adapters.ExtImpBidder",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/status-204.json b/adapters/copper6ssp/copper6ssptest/supplemental/status-204.json
new file mode 100644
index 00000000000..7dd1c65fd36
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/status-204.json
@@ -0,0 +1,80 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://example.com",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 204,
+			"body": {}
+		}
+    }],
+    "expectedBidResponses": []
+}
diff --git a/adapters/copper6ssp/copper6ssptest/supplemental/status-not-200.json b/adapters/copper6ssp/copper6ssptest/supplemental/status-not-200.json
new file mode 100644
index 00000000000..743f2996260
--- /dev/null
+++ b/adapters/copper6ssp/copper6ssptest/supplemental/status-not-200.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://example.com",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/copper6ssp/params_test.go b/adapters/copper6ssp/params_test.go
new file mode 100644
index 00000000000..f067a5f9d5c
--- /dev/null
+++ b/adapters/copper6ssp/params_test.go
@@ -0,0 +1,47 @@
+package copper6ssp
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderCopper6ssp, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderCopper6ssp, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "test"}`,
+	`{"placementId": "1"}`,
+	`{"endpointId": "test"}`,
+	`{"endpointId": "1"}`,
+}
+
+var invalidParams = []string{
+	`{"placementId": 42}`,
+	`{"endpointId": 42}`,
+	`{"placementId": "1", "endpointId": "1"}`,
+}
diff --git a/adapters/displayio/displayio.go b/adapters/displayio/displayio.go
new file mode 100644
index 00000000000..b54998553b3
--- /dev/null
+++ b/adapters/displayio/displayio.go
@@ -0,0 +1,188 @@
+package displayio
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/macros"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint *template.Template
+}
+
+type reqDioExt struct {
+	UserSession string `json:"userSession,omitempty"`
+	PlacementId string `json:"placementId"`
+	InventoryId string `json:"inventoryId"`
+}
+
+func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	headers.Add("x-openrtb-version", "2.5")
+
+	result := make([]*adapters.RequestData, 0, len(request.Imp))
+	errs := make([]error, 0, len(request.Imp))
+
+	for _, impression := range request.Imp {
+		var requestExt map[string]interface{}
+
+		if impression.BidFloorCur == "" || impression.BidFloor == 0 {
+			impression.BidFloorCur = "USD"
+		} else if impression.BidFloorCur != "USD" {
+			convertedValue, err := requestInfo.ConvertCurrency(impression.BidFloor, impression.BidFloorCur, "USD")
+
+			if err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			impression.BidFloor = convertedValue
+			impression.BidFloorCur = "USD"
+		}
+
+		if len(impression.Ext) == 0 {
+			errs = append(errs, errors.New("impression extensions required"))
+			continue
+		}
+
+		var bidderExt adapters.ExtImpBidder
+		err := json.Unmarshal(impression.Ext, &bidderExt)
+
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		var impressionExt openrtb_ext.ExtImpDisplayio
+		err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		dioExt := reqDioExt{PlacementId: impressionExt.PlacementId, InventoryId: impressionExt.InventoryId}
+
+		requestCopy := *request
+
+		err = json.Unmarshal(requestCopy.Ext, &requestExt)
+		if err != nil {
+			requestExt = make(map[string]interface{})
+		}
+
+		requestExt["displayio"] = dioExt
+
+		requestCopy.Ext, err = json.Marshal(requestExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		requestCopy.Imp = []openrtb2.Imp{impression}
+		body, err := json.Marshal(requestCopy)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		url, err := adapter.buildEndpointURL(&impressionExt)
+		if err != nil {
+			return nil, []error{err}
+		}
+
+		result = append(result, &adapters.RequestData{
+			Method:  "POST",
+			Uri:     url,
+			Body:    body,
+			Headers: headers,
+			ImpIDs:  openrtb_ext.GetImpIDs(requestCopy.Imp),
+		})
+	}
+
+	if len(result) == 0 {
+		return nil, errs
+	}
+	return result, errs
+}
+
+// MakeBids translates Displayio bid response to prebid-server specific format
+func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var bidResp openrtb2.BidResponse
+
+	if err := json.Unmarshal(responseData.Body, &bidResp); err != nil {
+		msg := fmt.Sprintf("Bad server response: %d", err)
+		return nil, []error{&errortypes.BadServerResponse{Message: msg}}
+	}
+
+	if len(bidResp.SeatBid) != 1 {
+		msg := fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))
+		return nil, []error{&errortypes.BadServerResponse{Message: msg}}
+	}
+
+	var errs []error
+	bidResponse := adapters.NewBidderResponse()
+
+	for _, sb := range bidResp.SeatBid {
+		for i := range sb.Bid {
+			bidType, err := getBidMediaTypeFromMtype(&sb.Bid[i])
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				b := &adapters.TypedBid{
+					Bid:     &sb.Bid[i],
+					BidType: bidType,
+				}
+				bidResponse.Bids = append(bidResponse.Bids, b)
+			}
+		}
+	}
+
+	return bidResponse, errs
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+	endpoint, 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: endpoint,
+	}
+	return bidder, nil
+}
+
+func getBidMediaTypeFromMtype(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	default:
+		return "", fmt.Errorf("unexpected media type for bid: %s", bid.ImpID)
+	}
+}
+
+func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpDisplayio) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherId}
+	return macros.ResolveMacros(adapter.endpoint, endpointParams)
+}
diff --git a/adapters/escalax/escalax.go b/adapters/escalax/escalax.go
new file mode 100644
index 00000000000..87cec5e17a9
--- /dev/null
+++ b/adapters/escalax/escalax.go
@@ -0,0 +1,162 @@
+package escalax
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/macros"
+	"github.com/prebid/prebid-server/v2/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) {
+	escalaxExt, err := getImpressionExt(&openRTBRequest.Imp[0])
+	if err != nil {
+		return nil, []error{err}
+	}
+
+	openRTBRequest.Imp[0].Ext = nil
+
+	url, err := a.buildEndpointURL(escalaxExt)
+	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),
+		ImpIDs:  openrtb_ext.GetImpIDs(openRTBRequest.Imp),
+	}}, nil
+}
+
+func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtEscalax, error) {
+	var bidderExt adapters.ExtImpBidder
+	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error parsing escalaxExt - " + err.Error(),
+		}
+	}
+	var escalaxExt openrtb_ext.ExtEscalax
+	if err := json.Unmarshal(bidderExt.Bidder, &escalaxExt); err != nil {
+		return nil, &errortypes.BadInput{
+			Message: "Error parsing bidderExt - " + err.Error(),
+		}
+	}
+
+	return &escalaxExt, nil
+}
+
+func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtEscalax) (string, error) {
+	endpointParams := macros.EndpointTemplateParams{AccountID: params.AccountID, SourceId: params.SourceID}
+	return macros.ResolveMacros(a.endpoint, endpointParams)
+}
+
+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
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(bidderRawResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	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 := determineImpressionMediaType(bid)
+			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 determineImpressionMediaType(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	default:
+		return "", &errortypes.BadInput{
+			Message: fmt.Sprintf("unsupported MType %d", bid.MType),
+		}
+	}
+}
diff --git a/adapters/escalax/escalax_test.go b/adapters/escalax/escalax_test.go
new file mode 100644
index 00000000000..dd1200ff61b
--- /dev/null
+++ b/adapters/escalax/escalax_test.go
@@ -0,0 +1,28 @@
+package escalax
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderEscalax, config.Adapter{
+		Endpoint: "http://bidder_us.escalax.io/?partner={{.SourceId}}&token={{.AccountID}}&type=pbs"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "escalaxtest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderEscalax, config.Adapter{
+		Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/escalax/escalaxtest/exemplary/banner-app.json b/adapters/escalax/escalaxtest/exemplary/banner-app.json
new file mode 100644
index 00000000000..e76ca1e291c
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/banner-app.json
@@ -0,0 +1,155 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "1"
+        ]
+      },
+      "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,
+                  "mtype": 1
+                }
+              ],
+              "type": "banner",
+              "seat": "escalax"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "1",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "adomain": [
+              "awesome.com"
+            ],
+            "crid": "20",
+            "w": 320,
+            "h": 50,
+            "mtype": 1
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/exemplary/banner-web.json b/adapters/escalax/escalaxtest/exemplary/banner-web.json
new file mode 100644
index 00000000000..28432e46f76
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/banner-web.json
@@ -0,0 +1,203 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      },
+      {
+        "id": "some-impression-id2",
+        "tagid": "ogTAGID",
+        "banner": {
+          "w": 320,
+          "h": 50
+        },
+        "ext": {
+          "bidder": {
+            "accountId": "accountId",
+            "sourceId": "sourceId",
+            "host": "host"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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",
+                  "sourceId": "sourceId",
+                  "host": "host"
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.com",
+            "publisher": {
+              "id": "123456789"
+            }
+          },
+          "user": {
+            "buyeruid": "awesome-user"
+          },
+          "tmax": 1000
+        },
+        "impIDs": [
+          "some-impression-id1",
+          "some-impression-id2"
+        ]
+      },
+      "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,
+                  "mtype": 1
+                },
+                {
+                  "id": "a3ae1b4e2fc24a4fb45540082e98e162",
+                  "impid": "some-impression-id2",
+                  "price": 3.5,
+                  "adm": "awesome-markup",
+                  "adomain": [
+                    "awesome.com"
+                  ],
+                  "crid": "20",
+                  "w": 320,
+                  "h": 50,
+                  "mtype": 1
+                }
+              ],
+              "type": "banner",
+              "seat": "escalax"
+            }
+          ],
+          "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,
+            "mtype": 1
+          },
+          "type": "banner"
+        },
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e162",
+            "impid": "some-impression-id2",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "w": 320,
+            "h": 50,
+            "mtype": 1
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/exemplary/native-app.json b/adapters/escalax/escalaxtest/exemplary/native-app.json
new file mode 100644
index 00000000000..27d47d9d19b
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/native-app.json
@@ -0,0 +1,151 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "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",
+                  "mtype": 4
+                }
+              ],
+              "type": "native",
+              "seat": "escalax"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "mtype": 4
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/exemplary/native-web.json b/adapters/escalax/escalaxtest/exemplary/native-web.json
new file mode 100644
index 00000000000..4480d14d11e
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/native-web.json
@@ -0,0 +1,138 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "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",
+                  "mtype": 4
+                }
+              ],
+              "seat": "escalax"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "bids": [
+        {
+          "bid": {
+            "id": "a3ae1b4e2fc24a4fb45540082e98e161",
+            "impid": "some-impression-id",
+            "price": 3.5,
+            "adm": "awesome-markup",
+            "crid": "20",
+            "adomain": [
+              "awesome.com"
+            ],
+            "mtype": 4
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/exemplary/video-app.json b/adapters/escalax/escalaxtest/exemplary/video-app.json
new file mode 100644
index 00000000000..2c23fb27aa3
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/video-app.json
@@ -0,0 +1,164 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "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,
+                  "mtype": 2
+                }
+              ],
+              "seat": "escalax"
+            }
+          ],
+          "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,
+            "mtype": 2
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/exemplary/video-web.json b/adapters/escalax/escalaxtest/exemplary/video-web.json
new file mode 100644
index 00000000000..4204767f794
--- /dev/null
+++ b/adapters/escalax/escalaxtest/exemplary/video-web.json
@@ -0,0 +1,162 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "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,
+                  "mtype": 2,
+                  "ext": {
+                    "prebid": {
+                      "type": "video"
+                    }
+                  }
+                }
+              ],
+              "seat": "escalax"
+            }
+          ],
+          "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,
+            "mtype": 2,
+            "ext": {
+              "prebid": {
+                "type": "video"
+              }
+            }
+          },
+          "type": "video"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/bad_media_type.json b/adapters/escalax/escalaxtest/supplemental/bad_media_type.json
new file mode 100644
index 00000000000..b98d680cb2e
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/bad_media_type.json
@@ -0,0 +1,139 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "1"
+        ]
+      },
+      "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,
+                  "mtype": 0
+                }
+              ],
+              "type": "banner",
+              "seat": "escalax"
+            }
+          ],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "unsupported MType 0",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/empty-seatbid-array.json b/adapters/escalax/escalaxtest/supplemental/empty-seatbid-array.json
new file mode 100644
index 00000000000..a7b8c12194e
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/empty-seatbid-array.json
@@ -0,0 +1,133 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "awesome-resp-id",
+          "seatbid": [],
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "mockResponse": {
+    "status": 200,
+    "body": "invalid response"
+  },
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Empty SeatBid array",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/invalid-bidder-ext-object.json b/adapters/escalax/escalaxtest/supplemental/invalid-bidder-ext-object.json
new file mode 100644
index 00000000000..96a3037ced0
--- /dev/null
+++ b/adapters/escalax/escalaxtest/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.ExtEscalax",
+      "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/escalax/escalaxtest/supplemental/invalid-ext-object.json b/adapters/escalax/escalaxtest/supplemental/invalid-ext-object.json
new file mode 100644
index 00000000000..f3725c1005d
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/invalid-ext-object.json
@@ -0,0 +1,31 @@
+{
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "Error parsing escalaxExt - 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": "wrongEscalaxExt"
+      }
+    ],
+    "site": {
+      "page": "test.com"
+    }
+  },
+  "httpCalls": []
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/invalid-response.json b/adapters/escalax/escalaxtest/supplemental/invalid-response.json
new file mode 100644
index 00000000000..f511eafa980
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/invalid-response.json
@@ -0,0 +1,115 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "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://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": "invalid response"
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Bad Server Response",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/status-code-bad-request.json b/adapters/escalax/escalaxtest/supplemental/status-code-bad-request.json
new file mode 100644
index 00000000000..d6537aac95e
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/status-code-bad-request.json
@@ -0,0 +1,96 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 400
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/escalaxtest/supplemental/status-code-other-error.json b/adapters/escalax/escalaxtest/supplemental/status-code-other-error.json
new file mode 100644
index 00000000000..00d618e5b29
--- /dev/null
+++ b/adapters/escalax/escalaxtest/supplemental/status-code-other-error.json
@@ -0,0 +1,84 @@
+{
+  "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",
+            "sourceId": "sourceId"
+          }
+        }
+      }
+    ]
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "http://bidder_us.escalax.io/?partner=sourceId&token=accountId&type=pbs",
+        "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
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 306
+      }
+    }
+  ],
+  "expectedBidResponses": [],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 306. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/escalax/params_test.go b/adapters/escalax/params_test.go
new file mode 100644
index 00000000000..a61af936055
--- /dev/null
+++ b/adapters/escalax/params_test.go
@@ -0,0 +1,52 @@
+package escalax
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+var validParams = []string{
+	`{ "sourceId": "someSourceId", "accountId": "0800fc577294" }`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`9`,
+	`1.2`,
+	`[]`,
+	`{}`,
+	`{  "accountId": "", "sourceId": "" }`,
+	`{  "accountId": true, "sourceId": true }`,
+	`{  "accountId": 123, "sourceId": 123 }`,
+	`{  "accountId": null, "sourceId": null }`,
+}
+
+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.BidderEscalax, json.RawMessage(validParam)); err != nil {
+			t.Errorf("Schema rejected Escalax params: %s", validParam)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json-schemas. %v", err)
+	}
+
+	for _, invalidParam := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderEscalax, json.RawMessage(invalidParam)); err == nil {
+			t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+		}
+	}
+}
diff --git a/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json
index e6838240ca4..18a1dbdbdef 100644
--- a/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json
+++ b/adapters/freewheelssp/freewheelssptest/exemplary/multi-imp.json
@@ -72,7 +72,8 @@
           "Componentid": [
             "prebid-go"
           ]
-        }
+        },
+        "impIDs":["imp-1","imp-2"]
       },
       "mockResponse": {
         "status": 200,
@@ -88,7 +89,10 @@
                   "adid": "7857",
                   "adm": "<?xml version='1.0' encoding='UTF-8'?><VAST version='2.0'></VAST>",
                   "cid": "4001",
-                  "crid": "7857"
+                  "crid": "7857",
+                  "adomain":["freewheel.com"],
+                  "cat": ["IAB10"],
+                  "dur": 14
                 },
                 {
                   "id": "12346_freewheelssp-test_2",
@@ -97,7 +101,9 @@
                   "adid": "7933",
                   "adm": "<?xml version='1.0' encoding='UTF-8'?><VAST version='2.0'></VAST>",
                   "cid": "3476",
-                  "crid": "7933"
+                  "crid": "7933",
+                  "adomain":["freewheel.com"],
+                  "dur":10
                 }
               ],
               "seat": "freewheelsspTv"
@@ -122,9 +128,16 @@
             "adid": "7857",
             "adm": "<?xml version='1.0' encoding='UTF-8'?><VAST version='2.0'></VAST>",
             "cid": "4001",
-            "crid": "7857"
+            "crid": "7857",
+            "adomain":["freewheel.com"],
+            "cat": ["IAB10"],
+            "dur": 14
           },
-          "type": "video"
+          "type": "video",
+          "video" : {
+            "duration" : 14,
+            "primary_category": "IAB10"
+          }
         },
         {
           "bid":  {
@@ -134,9 +147,15 @@
             "adid": "7933",
             "adm": "<?xml version='1.0' encoding='UTF-8'?><VAST version='2.0'></VAST>",
             "cid": "3476",
-            "crid": "7933"
+            "crid": "7933",
+            "adomain":["freewheel.com"],
+            "dur":10
           },
-          "type": "video"
+          "type": "video",
+          "video" : {
+            "duration" : 10,
+            "primary_category": ""
+          }
         }
       ]
     }
diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go
index 0fb06d3c6bf..57ffae5385a 100644
--- a/adapters/gumgum/gumgum.go
+++ b/adapters/gumgum/gumgum.go
@@ -160,12 +160,8 @@ func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) {
 	}
 
 	if imp.Video != nil {
-		err := validateVideoParams(imp.Video)
-		if err != nil {
-			return nil, err
-		}
-
 		if gumgumExt.IrisID != "" {
+			var err error
 			videoCopy := *imp.Video
 			videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: gumgumExt.IrisID}
 			videoCopy.Ext, err = json.Marshal(&videoExt)
@@ -220,15 +216,6 @@ func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType
 	return openrtb_ext.BidTypeVideo
 }
 
-func validateVideoParams(video *openrtb2.Video) (err error) {
-	if video.W == nil || *video.W == 0 || video.H == nil || *video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 {
-		return &errortypes.BadInput{
-			Message: "Invalid or missing video field(s)",
-		}
-	}
-	return nil
-}
-
 // Builder builds a new instance of the GumGum adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &adapter{
diff --git a/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json b/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json
deleted file mode 100644
index b2475cd7bb4..00000000000
--- a/adapters/gumgum/gumgumtest/supplemental/missing-video-params.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
-    "mockBidRequest": {
-      "id": "test-request-id",
-      "imp": [
-        {
-            "id": "test-imp-id",
-            "video": {
-                "mimes": [
-                    "video/mp4"
-                ],
-                "protocols": [
-                    1,
-                    2
-                ],
-                "w": 640,
-                "h": 480,
-                "startdelay": 1,
-                "placement": 1,
-                "linearity": 1
-            },
-            "ext": {
-                "bidder": {
-                    "zone": "ggumtest"
-                }
-            }
-        }
-    ]
-    },
-    "expectedMakeRequestsErrors": [
-      {
-        "value": "Invalid or missing video field(s)",
-        "comparison": "literal"
-      }
-    ]
-  }
-  
\ No newline at end of file
diff --git a/adapters/gumgum/gumgumtest/supplemental/video-missing-size.json b/adapters/gumgum/gumgumtest/supplemental/video-missing-size.json
deleted file mode 100644
index 1e4afe167ea..00000000000
--- a/adapters/gumgum/gumgumtest/supplemental/video-missing-size.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "mockBidRequest": {
-    "id": "test-request-id",
-    "imp": [
-      {
-        "id": "test-imp-id",
-        "video": {
-          "mimes": [
-            "video/mp4"
-          ],
-          "minduration": 1,
-          "maxduration": 2,
-          "protocols": [
-            1,
-            2
-          ],
-          "startdelay": 1,
-          "placement": 1,
-          "linearity": 1
-        },
-        "ext": {
-          "bidder": {
-            "zone": "ggumtest"
-          }
-        }
-      }
-    ]
-  },
-  "expectedMakeRequestsErrors": [
-    {
-      "value": "Invalid or missing video field(s)",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/gumgum/gumgumtest/supplemental/video-partial-size.json b/adapters/gumgum/gumgumtest/supplemental/video-partial-size.json
index 3c9727a1a9c..ce43a93b145 100644
--- a/adapters/gumgum/gumgumtest/supplemental/video-partial-size.json
+++ b/adapters/gumgum/gumgumtest/supplemental/video-partial-size.json
@@ -27,10 +27,79 @@
       }
     ]
   },
-  "expectedMakeRequestsErrors": [
+  "httpCalls": [
     {
-      "value": "Invalid or missing video field(s)",
-      "comparison": "literal"
+        "expectedRequest": {
+            "uri": "https://g2.gumgum.com/providers/prbds2s/bid",
+            "body": {
+              "id": "test-request-id",
+              "imp": [
+                {
+                  "id": "test-imp-id",
+                  "video": {
+                    "mimes": [
+                      "video/mp4"
+                    ],
+                    "minduration": 1,
+                    "maxduration": 2,
+                    "protocols": [
+                      1,
+                      2
+                    ],
+                    "w": 640,
+                    "startdelay": 1,
+                    "placement": 1,
+                    "linearity": 1
+                  },
+                  "ext": {
+                    "bidder": {
+                      "zone": "ggumtest"
+                    }
+                  }
+                }
+              ]
+            },
+            "impIDs":["test-imp-id"]
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "15da721e-940a-4db6-8621-a1f93140b21b",
+                                "impid": "video1",
+                                "price": 15,
+                                "adid": "59082",
+                                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\">\n    <Ad id=\"59082\">\n        <InLine>\n            <AdSystem>GumGum Video</AdSystem>\n            <AdTitle>\n                <![CDATA[\n                Pre-Roll Preview - Olay - Linear (:15)\n                ]]>\n            </AdTitle>\n            <Impression>\n                <![CDATA[ \n                http://or-g2.gumgum.com/ad/apvideo/impression?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                ]]>\n            </Impression>\n            <Creatives>\n                <Creative>\n                    <Linear>\n                        <Duration>00:00:15</Duration>\n                        <TrackingEvents>\n                            <Tracking event=\"start\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/play?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"firstQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed25?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"midpoint\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed50?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"thirdQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed75?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"complete\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed100?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                        </TrackingEvents>\n                        <VideoClicks>\n                            <ClickThrough>\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/click/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQICKHpXkDscfR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qWEwNqd4GFjWm0S6Hmy18v7I3SrGJPg7FzC1iGGOSMjfe1DJDplloE0qQcxKI9gI1aTJqG_G7PY6QP1Gz87Ojj7mLf40udAlRofZaq1FRdFXXgo0XIeg3J7o07-2TGPvBfdg0hIpis6TOWo-gcqbOKWOP24eiroudFCTDNH9QAi9dXKor0jUMLhKmvY6kvmNl8?du=\n                                ]]>\n                            </ClickThrough>\n                        </VideoClicks>\n                        <MediaFiles>\n                            <MediaFile bitrate=\"800\" delivery=\"progressive\" height=\"9\" type=\"video/mp4\" width=\"16\">\n                                <![CDATA[ https://c.gumgum.com/ads/com/procter_gamble/olay_daily_facials_q2_2017/pre_roll_demo/5_source_41397_100091.mp4\n                                ]]>\n                            </MediaFile>\n                        </MediaFiles>\n                    </Linear>\n                </Creative>\n            </Creatives>\n        <Impression><![CDATA[http://or-g2.gumgum.com/ad/view/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQJ8jwoXY9Y_XR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qXaMMkNa6YwX71oVLIAHL6p6DL-8M2FwYgSPFHW1P72slMYnDS0_PfnysQjnRJ2eQXGmpiIEg0seK48nGbq14RwpQtD8WOwrIySXeuk0TqdyUQE2RMlq2dV?c=15]]></Impression><Error><![CDATA[http://or-g2.gumgum.com/video/error?code=[ERRORCODE]&adid=59082&t=ggumtest]]></Error></InLine>\n    </Ad>\n</VAST>",
+                                "cid": "3579",
+                                "crid": "59082"
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    }
+],
+"expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+            {
+                "bid": {
+                    "id": "15da721e-940a-4db6-8621-a1f93140b21b",
+                    "impid": "video1",
+                    "price": 15,
+                    "adid": "59082",
+                    "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\">\n    <Ad id=\"59082\">\n        <InLine>\n            <AdSystem>GumGum Video</AdSystem>\n            <AdTitle>\n                <![CDATA[\n                Pre-Roll Preview - Olay - Linear (:15)\n                ]]>\n            </AdTitle>\n            <Impression>\n                <![CDATA[ \n                http://or-g2.gumgum.com/ad/apvideo/impression?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                ]]>\n            </Impression>\n            <Creatives>\n                <Creative>\n                    <Linear>\n                        <Duration>00:00:15</Duration>\n                        <TrackingEvents>\n                            <Tracking event=\"start\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/play?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"firstQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed25?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"midpoint\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed50?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"thirdQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed75?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"complete\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed100?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                        </TrackingEvents>\n                        <VideoClicks>\n                            <ClickThrough>\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/click/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQICKHpXkDscfR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qWEwNqd4GFjWm0S6Hmy18v7I3SrGJPg7FzC1iGGOSMjfe1DJDplloE0qQcxKI9gI1aTJqG_G7PY6QP1Gz87Ojj7mLf40udAlRofZaq1FRdFXXgo0XIeg3J7o07-2TGPvBfdg0hIpis6TOWo-gcqbOKWOP24eiroudFCTDNH9QAi9dXKor0jUMLhKmvY6kvmNl8?du=\n                                ]]>\n                            </ClickThrough>\n                        </VideoClicks>\n                        <MediaFiles>\n                            <MediaFile bitrate=\"800\" delivery=\"progressive\" height=\"9\" type=\"video/mp4\" width=\"16\">\n                                <![CDATA[ https://c.gumgum.com/ads/com/procter_gamble/olay_daily_facials_q2_2017/pre_roll_demo/5_source_41397_100091.mp4\n                                ]]>\n                            </MediaFile>\n                        </MediaFiles>\n                    </Linear>\n                </Creative>\n            </Creatives>\n        <Impression><![CDATA[http://or-g2.gumgum.com/ad/view/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQJ8jwoXY9Y_XR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qXaMMkNa6YwX71oVLIAHL6p6DL-8M2FwYgSPFHW1P72slMYnDS0_PfnysQjnRJ2eQXGmpiIEg0seK48nGbq14RwpQtD8WOwrIySXeuk0TqdyUQE2RMlq2dV?c=15]]></Impression><Error><![CDATA[http://or-g2.gumgum.com/video/error?code=[ERRORCODE]&adid=59082&t=ggumtest]]></Error></InLine>\n    </Ad>\n</VAST>",
+                    "cid": "3579",
+                    "crid": "59082"
+                },
+                "type": "video"
+            }
+        ]
     }
-  ]
+]
 }
\ No newline at end of file
diff --git a/adapters/gumgum/gumgumtest/supplemental/video-zero-size.json b/adapters/gumgum/gumgumtest/supplemental/video-zero-size.json
index d3d4b427120..4fc7dc3ce77 100644
--- a/adapters/gumgum/gumgumtest/supplemental/video-zero-size.json
+++ b/adapters/gumgum/gumgumtest/supplemental/video-zero-size.json
@@ -28,10 +28,80 @@
       }
     ]
   },
-  "expectedMakeRequestsErrors": [
+  "httpCalls": [
     {
-      "value": "Invalid or missing video field(s)",
-      "comparison": "literal"
+        "expectedRequest": {
+            "uri": "https://g2.gumgum.com/providers/prbds2s/bid",
+            "body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "video": {
+                          "mimes": [
+                            "video/mp4"
+                          ],
+                          "minduration": 1,
+                          "maxduration": 2,
+                          "protocols": [
+                            1,
+                            2
+                          ],
+                          "w": 0,
+                          "h": 0,
+                          "startdelay": 1,
+                          "placement": 1,
+                          "linearity": 1
+                        },
+                        "ext": {
+                          "bidder": {
+                            "zone": "ggumtest"
+                          }
+                        }
+                      }
+                ]
+            },
+            "impIDs":["test-imp-id"]
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "15da721e-940a-4db6-8621-a1f93140b21b",
+                                "impid": "video1",
+                                "price": 15,
+                                "adid": "59082",
+                                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\">\n    <Ad id=\"59082\">\n        <InLine>\n            <AdSystem>GumGum Video</AdSystem>\n            <AdTitle>\n                <![CDATA[\n                Pre-Roll Preview - Olay - Linear (:15)\n                ]]>\n            </AdTitle>\n            <Impression>\n                <![CDATA[ \n                http://or-g2.gumgum.com/ad/apvideo/impression?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                ]]>\n            </Impression>\n            <Creatives>\n                <Creative>\n                    <Linear>\n                        <Duration>00:00:15</Duration>\n                        <TrackingEvents>\n                            <Tracking event=\"start\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/play?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"firstQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed25?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"midpoint\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed50?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"thirdQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed75?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"complete\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed100?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                        </TrackingEvents>\n                        <VideoClicks>\n                            <ClickThrough>\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/click/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQICKHpXkDscfR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qWEwNqd4GFjWm0S6Hmy18v7I3SrGJPg7FzC1iGGOSMjfe1DJDplloE0qQcxKI9gI1aTJqG_G7PY6QP1Gz87Ojj7mLf40udAlRofZaq1FRdFXXgo0XIeg3J7o07-2TGPvBfdg0hIpis6TOWo-gcqbOKWOP24eiroudFCTDNH9QAi9dXKor0jUMLhKmvY6kvmNl8?du=\n                                ]]>\n                            </ClickThrough>\n                        </VideoClicks>\n                        <MediaFiles>\n                            <MediaFile bitrate=\"800\" delivery=\"progressive\" height=\"9\" type=\"video/mp4\" width=\"16\">\n                                <![CDATA[ https://c.gumgum.com/ads/com/procter_gamble/olay_daily_facials_q2_2017/pre_roll_demo/5_source_41397_100091.mp4\n                                ]]>\n                            </MediaFile>\n                        </MediaFiles>\n                    </Linear>\n                </Creative>\n            </Creatives>\n        <Impression><![CDATA[http://or-g2.gumgum.com/ad/view/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQJ8jwoXY9Y_XR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qXaMMkNa6YwX71oVLIAHL6p6DL-8M2FwYgSPFHW1P72slMYnDS0_PfnysQjnRJ2eQXGmpiIEg0seK48nGbq14RwpQtD8WOwrIySXeuk0TqdyUQE2RMlq2dV?c=15]]></Impression><Error><![CDATA[http://or-g2.gumgum.com/video/error?code=[ERRORCODE]&adid=59082&t=ggumtest]]></Error></InLine>\n    </Ad>\n</VAST>",
+                                "cid": "3579",
+                                "crid": "59082"
+                            }
+                        ]
+                    }
+                ]
+            }
+        }
+    }
+],
+"expectedBidResponses": [
+    {
+        "currency": "USD",
+        "bids": [
+            {
+                "bid": {
+                    "id": "15da721e-940a-4db6-8621-a1f93140b21b",
+                    "impid": "video1",
+                    "price": 15,
+                    "adid": "59082",
+                    "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\">\n    <Ad id=\"59082\">\n        <InLine>\n            <AdSystem>GumGum Video</AdSystem>\n            <AdTitle>\n                <![CDATA[\n                Pre-Roll Preview - Olay - Linear (:15)\n                ]]>\n            </AdTitle>\n            <Impression>\n                <![CDATA[ \n                http://or-g2.gumgum.com/ad/apvideo/impression?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                ]]>\n            </Impression>\n            <Creatives>\n                <Creative>\n                    <Linear>\n                        <Duration>00:00:15</Duration>\n                        <TrackingEvents>\n                            <Tracking event=\"start\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/play?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"firstQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed25?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"midpoint\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed50?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"thirdQuartile\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed75?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                            <Tracking event=\"complete\">\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/apvideo/completed100?vpi=1&t=ggumtest&ab=59082&pv=44df4775-15b1-4162-9970-43d06a519e34&pu=https%3A%2F%2Fwww.100daysofrealfood.com%2F&zdp=*&iimpid=16ddc020-1705-4351-9951-467474c8f385&lt=&to=&ts=1594835221365&er=0&pspc=prbds2s&jcsi=%7B%22t%22%3A1%2C%22rq%22%3A12%2C%22pbv%22%3A%220.0.0%22%7D&pbf=0.0\n                                ]]>\n                            </Tracking>\n                        </TrackingEvents>\n                        <VideoClicks>\n                            <ClickThrough>\n                                <![CDATA[\n                                http://or-g2.gumgum.com/ad/click/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQICKHpXkDscfR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qWEwNqd4GFjWm0S6Hmy18v7I3SrGJPg7FzC1iGGOSMjfe1DJDplloE0qQcxKI9gI1aTJqG_G7PY6QP1Gz87Ojj7mLf40udAlRofZaq1FRdFXXgo0XIeg3J7o07-2TGPvBfdg0hIpis6TOWo-gcqbOKWOP24eiroudFCTDNH9QAi9dXKor0jUMLhKmvY6kvmNl8?du=\n                                ]]>\n                            </ClickThrough>\n                        </VideoClicks>\n                        <MediaFiles>\n                            <MediaFile bitrate=\"800\" delivery=\"progressive\" height=\"9\" type=\"video/mp4\" width=\"16\">\n                                <![CDATA[ https://c.gumgum.com/ads/com/procter_gamble/olay_daily_facials_q2_2017/pre_roll_demo/5_source_41397_100091.mp4\n                                ]]>\n                            </MediaFile>\n                        </MediaFiles>\n                    </Linear>\n                </Creative>\n            </Creatives>\n        <Impression><![CDATA[http://or-g2.gumgum.com/ad/view/enc/_EFZ4AbNn_bHPd6fKLhwJUB_IyBmxpaj9eCAt4yjP8Lh-lInPc1v8-nOmpntWjYalCcBciU4c_QPQhN9g9uzF2lz0QBdNTDxiK5Q18wC0JZkSvT0xe4L6_C9mgmmP8BOUWcDV6bbhrCPr6OF6AHLlzcp2MaV4IJ-c6wh2MPyRbRvy5pxSHf-KbYM172krOmxW-37VSxq2yPDjQxDuUwd9XqxKUTXIYzDW_5VTwCzYCyus5vyPMNGQJ8jwoXY9Y_XR2EadcO2qRKhqC65pegIsnUg-Eq0Ey4rp7RLiPFjl0WjXO88KlRIgHMgd2-JYcHIumNnuYcy-hpr787Sk_Ejp_tMVujc6b4Cg634_YR52qXaMMkNa6YwX71oVLIAHL6p6DL-8M2FwYgSPFHW1P72slMYnDS0_PfnysQjnRJ2eQXGmpiIEg0seK48nGbq14RwpQtD8WOwrIySXeuk0TqdyUQE2RMlq2dV?c=15]]></Impression><Error><![CDATA[http://or-g2.gumgum.com/video/error?code=[ERRORCODE]&adid=59082&t=ggumtest]]></Error></InLine>\n    </Ad>\n</VAST>",
+                    "cid": "3579",
+                    "crid": "59082"
+                },
+                "type": "video"
+            }
+        ]
     }
-  ]
+]
 }
\ No newline at end of file
diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go
index 3fd514be86d..4c7ca6328cf 100644
--- a/adapters/improvedigital/improvedigital.go
+++ b/adapters/improvedigital/improvedigital.go
@@ -16,12 +16,9 @@ import (
 )
 
 const (
-	isRewardedInventory              = "is_rewarded_inventory"
-	stateRewardedInventoryEnable     = "1"
-	consentProvidersSettingsInputKey = "ConsentedProvidersSettings"
-	consentProvidersSettingsOutKey   = "consented_providers_settings"
-	consentedProvidersKey            = "consented_providers"
-	publisherEndpointParam           = "{PublisherId}"
+	isRewardedInventory          = "is_rewarded_inventory"
+	stateRewardedInventoryEnable = "1"
+	publisherEndpointParam       = "{PublisherId}"
 )
 
 type ImprovedigitalAdapter struct {
@@ -75,17 +72,6 @@ func (a *ImprovedigitalAdapter) makeRequest(request openrtb2.BidRequest, imp ope
 
 	request.Imp = []openrtb2.Imp{imp}
 
-	userExtAddtlConsent, err := a.getAdditionalConsentProvidersUserExt(request)
-	if err != nil {
-		return nil, err
-	}
-
-	if len(userExtAddtlConsent) > 0 {
-		userCopy := *request.User
-		userCopy.Ext = userExtAddtlConsent
-		request.User = &userCopy
-	}
-
 	reqJSON, err := json.Marshal(request)
 	if err != nil {
 		return nil, err
@@ -254,69 +240,6 @@ func isMultiFormatImp(imp openrtb2.Imp) bool {
 	return formatCount > 1
 }
 
-// This method responsible to clone request and convert additional consent providers string to array when additional consent provider found
-func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request openrtb2.BidRequest) ([]byte, error) {
-	var cpStr string
-
-	// If user/user.ext not defined, no need to parse additional consent
-	if request.User == nil || request.User.Ext == nil {
-		return nil, nil
-	}
-
-	// Start validating additional consent
-	// Check key exist user.ext.ConsentedProvidersSettings
-	var userExtMap = make(map[string]json.RawMessage)
-	if err := json.Unmarshal(request.User.Ext, &userExtMap); err != nil {
-		return nil, err
-	}
-
-	cpsMapValue, cpsJSONFound := userExtMap[consentProvidersSettingsInputKey]
-	if !cpsJSONFound {
-		return nil, nil
-	}
-
-	// Check key exist user.ext.ConsentedProvidersSettings.consented_providers
-	var cpMap = make(map[string]json.RawMessage)
-	if err := json.Unmarshal(cpsMapValue, &cpMap); err != nil {
-		return nil, err
-	}
-
-	cpMapValue, cpJSONFound := cpMap[consentedProvidersKey]
-	if !cpJSONFound {
-		return nil, nil
-	}
-	// End validating additional consent
-
-	// Trim enclosing quotes after casting json.RawMessage to string
-	consentStr := strings.Trim((string)(cpMapValue), "\"")
-	// Split by ~ and take only the second string (if exists) as the consented providers spec
-	var consentStrParts = strings.Split(consentStr, "~")
-	if len(consentStrParts) < 2 {
-		return nil, nil
-	}
-	cpStr = strings.TrimSpace(consentStrParts[1])
-	if len(cpStr) == 0 {
-		return nil, nil
-	}
-
-	// Prepare consent providers string
-	cpStr = fmt.Sprintf("[%s]", strings.Replace(cpStr, ".", ",", -1))
-	cpMap[consentedProvidersKey] = json.RawMessage(cpStr)
-
-	cpJSON, err := json.Marshal(cpMap)
-	if err != nil {
-		return nil, err
-	}
-	userExtMap[consentProvidersSettingsOutKey] = cpJSON
-
-	extJson, err := json.Marshal(userExtMap)
-	if err != nil {
-		return nil, err
-	}
-
-	return extJson, nil
-}
-
 func getImpExtWithRewardedInventory(imp openrtb2.Imp) ([]byte, error) {
 	var ext = make(map[string]json.RawMessage)
 	if err := json.Unmarshal(imp.Ext, &ext); err != nil {
diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent-multi-tilda.json b/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent-multi-tilda.json
deleted file mode 100644
index 24c4b813ff3..00000000000
--- a/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent-multi-tilda.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
-  "mockBidRequest": {
-    "id": "addtl-consent-request-id",
-    "site": {
-      "page": "https://good.site/url"
-    },
-    "imp": [{
-      "id": "test-imp-id",
-      "banner": {
-        "format": [{
-          "w": 300,
-          "h": 250
-        }]
-      },
-      "ext": {
-        "bidder": {
-          "placementId": 13245
-        }
-      }
-    }],
-    "user": {
-      "ext":{"consent":"ABC","ConsentedProvidersSettings":{"consented_providers":"1~10.20.90~2"}}
-    }
-  },
-
-  "httpCalls": [{
-    "expectedRequest": {
-      "uri": "http://localhost/pbs",
-      "body": {
-        "id": "addtl-consent-request-id",
-        "site": {
-          "page": "https://good.site/url"
-        },
-        "imp": [{
-          "id": "test-imp-id",
-          "banner": {
-            "format": [{
-              "w": 300,
-              "h": 250
-            }]
-          },
-          "ext": {
-            "bidder": {
-              "placementId": 13245
-            }
-          }
-        }],
-        "user": {
-          "ext": {"consent": "ABC","ConsentedProvidersSettings":{"consented_providers":"1~10.20.90~2"},"consented_providers_settings": {"consented_providers": [10,20,90]}
-          }
-        }
-      }
-    },
-    "mockResponse": {
-      "status": 200,
-      "body": {
-        "id": "addtl-consent-request-id",
-        "seatbid": [{
-          "seat": "improvedigital",
-          "bid": [{
-            "id": "randomid",
-            "impid": "test-imp-id",
-            "price": 0.500000,
-            "adid": "12345678",
-            "adm": "some-test-ad",
-            "cid": "987",
-            "crid": "12345678",
-            "h": 250,
-            "w": 300
-          }]
-        }],
-        "cur": "USD"
-      }
-    }
-  }],
-
-  "expectedBidResponses": [{
-    "currency": "USD",
-    "bids": [{
-      "bid": {
-        "id": "randomid",
-        "impid": "test-imp-id",
-        "price": 0.5,
-        "adm": "some-test-ad",
-        "adid": "12345678",
-        "cid": "987",
-        "crid": "12345678",
-        "w": 300,
-        "h": 250
-      },
-      "type": "banner"
-    }]
-  }]
-}
diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent.json b/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent.json
deleted file mode 100644
index b1b780f64ba..00000000000
--- a/adapters/improvedigital/improvedigitaltest/supplemental/addtl-consent.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
-  "mockBidRequest": {
-    "id": "addtl-consent-request-id",
-    "site": {
-      "page": "https://good.site/url"
-    },
-    "imp": [{
-      "id": "test-imp-id",
-      "banner": {
-        "format": [{
-          "w": 300,
-          "h": 250
-        }]
-      },
-      "ext": {
-        "bidder": {
-          "placementId": 13245
-        }
-      }
-    }],
-    "user": {
-      "ext":{"consent":"ABC","ConsentedProvidersSettings":{"consented_providers":"1~10.20.90"}}
-    }
-  },
-
-  "httpCalls": [{
-    "expectedRequest": {
-      "uri": "http://localhost/pbs",
-      "body": {
-        "id": "addtl-consent-request-id",
-        "site": {
-          "page": "https://good.site/url"
-        },
-        "imp": [{
-          "id": "test-imp-id",
-          "banner": {
-            "format": [{
-              "w": 300,
-              "h": 250
-            }]
-          },
-          "ext": {
-            "bidder": {
-              "placementId": 13245
-            }
-          }
-        }],
-        "user": {
-          "ext": {"consent": "ABC","ConsentedProvidersSettings":{"consented_providers":"1~10.20.90"},"consented_providers_settings": {"consented_providers": [10,20,90]}
-          }
-        }
-      }
-    },
-    "mockResponse": {
-      "status": 200,
-      "body": {
-        "id": "addtl-consent-request-id",
-        "seatbid": [{
-          "seat": "improvedigital",
-          "bid": [{
-            "id": "randomid",
-            "impid": "test-imp-id",
-            "price": 0.500000,
-            "adid": "12345678",
-            "adm": "some-test-ad",
-            "cid": "987",
-            "crid": "12345678",
-            "h": 250,
-            "w": 300
-          }]
-        }],
-        "cur": "USD"
-      }
-    }
-  }],
-
-  "expectedBidResponses": [{
-    "currency": "USD",
-    "bids": [{
-      "bid": {
-        "id": "randomid",
-        "impid": "test-imp-id",
-        "price": 0.5,
-        "adm": "some-test-ad",
-        "adid": "12345678",
-        "cid": "987",
-        "crid": "12345678",
-        "w": 300,
-        "h": 250
-      },
-      "type": "banner"
-    }]
-  }]
-}
diff --git a/adapters/improvedigital/params_test.go b/adapters/improvedigital/params_test.go
index 3767c0316a5..86c0611f162 100644
--- a/adapters/improvedigital/params_test.go
+++ b/adapters/improvedigital/params_test.go
@@ -36,7 +36,6 @@ func TestInvalidParams(t *testing.T) {
 var validParams = []string{
 	`{"placementId":13245}`,
 	`{"placementId":13245, "size": {"w":16, "h":9}}`,
-	`{"publisherId":13245, "placementKey": "slotA"}`,
 	`{"placementId":13245, "keyValues":{"target1":["foo"],"target2":["bar", "baz"]}}`,
 }
 
@@ -56,5 +55,5 @@ var invalidParams = []string{
 	`{"placementId": "1"}`,
 	`{"size": true}`,
 	`{"placementId": true, "size":"1234567"}`,
-	`{"placementId":13245, "publisherId":13245, "placementKey": "slotA"}`,
+	`{"publisherId":13245, "placementKey": "slotA"}`,
 }
diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go
index 3c20fdc5627..af6b9838832 100644
--- a/adapters/inmobi/inmobi.go
+++ b/adapters/inmobi/inmobi.go
@@ -76,7 +76,10 @@ func (a *InMobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR
 
 	for _, sb := range serverBidResponse.SeatBid {
 		for i := range sb.Bid {
-			mediaType := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
+			mediaType, err := getMediaTypeForImp(sb.Bid[i])
+			if err != nil {
+				return nil, []error{err}
+			}
 			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
 				Bid:     &sb.Bid[i],
 				BidType: mediaType,
@@ -117,18 +120,17 @@ func preprocess(imp *openrtb2.Imp) error {
 	return nil
 }
 
-func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
-	mediaType := openrtb_ext.BidTypeBanner
-	for _, imp := range imps {
-		if imp.ID == impId {
-			if imp.Video != nil {
-				mediaType = openrtb_ext.BidTypeVideo
-			}
-			if imp.Native != nil {
-				mediaType = openrtb_ext.BidTypeNative
-			}
-			break
+func getMediaTypeForImp(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	default:
+		return "", &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unsupported mtype %d for bid %s", bid.MType, bid.ID),
 		}
 	}
-	return mediaType
 }
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json
index 4345ef8ff66..a74d091fce6 100644
--- a/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json
+++ b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json
@@ -74,7 +74,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "bannerhtml",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 1
               }
             ]
           }
@@ -93,6 +94,7 @@
         "adm": "bannerhtml",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 1,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json
index 3a5bfd38412..cef76b65c6f 100644
--- a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json
+++ b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json
@@ -72,7 +72,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "native-json",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 4
               }
             ]
           }
@@ -91,6 +92,7 @@
         "adm": "native-json",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 4,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-video.json b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json
index 20b3c0cc810..162b1b97418 100644
--- a/adapters/inmobi/inmobitest/exemplary/simple-app-video.json
+++ b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json
@@ -76,7 +76,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 2
               }
             ]
           }
@@ -95,6 +96,7 @@
         "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 2,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json
index 131249ba8a1..493a47043b5 100644
--- a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json
+++ b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json
@@ -72,7 +72,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "bannerhtml",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 1
               }
             ]
           }
@@ -91,6 +92,7 @@
         "adm": "bannerhtml",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 1,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-native.json b/adapters/inmobi/inmobitest/exemplary/simple-web-native.json
new file mode 100644
index 00000000000..25313573ea8
--- /dev/null
+++ b/adapters/inmobi/inmobitest/exemplary/simple-web-native.json
@@ -0,0 +1,106 @@
+{
+  "mockBidRequest": {
+    "site": {
+      "page": "https://www.inmobi.com"
+    },
+    "id": "req-id",
+    "device": {
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1716021069867"
+          }
+        },
+        "native": {
+          "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}"
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "site": {
+          "page": "https://www.inmobi.com"
+        },
+        "id": "req-id",
+        "device": {
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1716021069867"
+              }
+            },
+            "native": {
+              "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}"
+            },
+            "id": "imp-id"
+          }
+        ]
+      },
+      "impIDs":["imp-id"]
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "req-id",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "ext": {
+                  "prebid": {
+                    "meta": {
+                      "networkName": "inmobi"
+                    }
+                  }
+                },
+                "nurl": "https://some.event.url/params",
+                "crid": "123456789",
+                "adomain": [],
+                "price": 2.0,
+                "id": "1234",
+                "adm": "native-json",
+                "impid": "imp-id",
+                "mtype": 4
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }],
+
+  "expectedBidResponses": [{
+    "currency": "USD",
+    "bids": [{
+      "bid": {
+        "id": "1234",
+        "impid": "imp-id",
+        "price": 2.0,
+        "adm": "native-json",
+        "crid": "123456789",
+        "nurl": "https://some.event.url/params",
+        "mtype": 4,
+        "ext": {
+          "prebid": {
+            "meta": {
+              "networkName": "inmobi"
+            }
+          }
+        }
+      },
+      "type": "native"
+    }]
+  }]
+}
diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json
index 3aed605f416..be27041e691 100644
--- a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json
+++ b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json
@@ -74,7 +74,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 2
               }
             ]
           }
@@ -93,6 +94,7 @@
         "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 2,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json
index 514f86817c9..9913e6431d0 100644
--- a/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json
+++ b/adapters/inmobi/inmobitest/supplemental/banner-format-coersion.json
@@ -80,7 +80,8 @@
                 "price": 2.0,
                 "id": "1234",
                 "adm": "bannerhtml",
-                "impid": "imp-id"
+                "impid": "imp-id",
+                "mtype": 1
               }
             ]
           }
@@ -99,6 +100,7 @@
         "adm": "bannerhtml",
         "crid": "123456789",
         "nurl": "https://some.event.url/params",
+        "mtype": 1,
         "ext": {
           "prebid": {
             "meta": {
diff --git a/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json b/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json
new file mode 100644
index 00000000000..af2192836b0
--- /dev/null
+++ b/adapters/inmobi/inmobitest/supplemental/invalid-mtype.json
@@ -0,0 +1,97 @@
+{
+  "mockBidRequest": {
+    "site": {
+      "page": "https://www.inmobi.com"
+    },
+    "id": "req-id",
+    "device": {
+      "ip": "1.1.1.1",
+      "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+    },
+    "imp": [
+      {
+        "ext": {
+          "bidder": {
+            "plc": "1621323101291"
+          }
+        },
+        "video": {
+          "w": 640,
+          "h": 360,
+          "mimes": ["video/mp4"]
+        },
+        "id": "imp-id"
+      }
+    ]
+  },
+  "httpCalls": [{
+    "expectedRequest": {
+      "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid",
+      "body": {
+        "site": {
+          "page": "https://www.inmobi.com"
+        },
+        "id": "req-id",
+        "device": {
+          "ip": "1.1.1.1",
+          "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+        },
+        "imp": [
+          {
+            "ext": {
+              "bidder": {
+                "plc": "1621323101291"
+              }
+            },
+            "video": {
+              "w": 640,
+              "h": 360,
+              "mimes": ["video/mp4"]
+            },
+            "id": "imp-id"
+          }
+        ]
+      },
+      "impIDs":["imp-id"]
+    },
+    "mockResponse": {
+      "status": 200,
+      "body": {
+        "id": "req-id",
+        "seatbid": [
+          {
+            "bid": [
+              {
+                "ext": {
+                  "prebid": {
+                    "meta": {
+                      "networkName": "inmobi"
+                    }
+                  }
+                },
+                "nurl": "https://some.event.url/params",
+                "crid": "123456789",
+                "adomain": [],
+                "price": 2.0,
+                "id": "1234",
+                "adm": "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <VAST version=\"3.0\"></VAST>",
+                "impid": "imp-id",
+                "mtype": 0
+              }
+            ]
+          }
+        ]
+      }
+    }
+  }],
+
+  "expectedBidResponses":[],
+  "expectedMakeBidsErrors":[
+    {
+      "value":"Unsupported mtype 0 for bid 1234",
+      "comparison":"literal"
+    }
+  ]
+}
+
+
diff --git a/adapters/lemmadigital/lemmadigital_test.go b/adapters/lemmadigital/lemmadigital_test.go
index e0062c0b565..cf507f34504 100644
--- a/adapters/lemmadigital/lemmadigital_test.go
+++ b/adapters/lemmadigital/lemmadigital_test.go
@@ -10,7 +10,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderLemmadigital, config.Adapter{
-		Endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint: "https://test.lemmaurl.com/lemma/servad?src=prebid&pid={{.PublisherID}}&aid={{.AdUnit}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	if buildErr != nil {
 		t.Fatalf("Builder returned unexpected error %v", buildErr)
diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json
index a478380e394..eab7fe5366b 100644
--- a/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json
+++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/banner.json
@@ -32,7 +32,7 @@
 
     "httpCalls": [{
         "expectedRequest": {
-            "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+            "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
             "body": {
                 "id": "test-request-id",
                 "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json
index e051b54ff95..ce160944122 100644
--- a/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json
+++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/multi-imp.json
@@ -49,7 +49,7 @@
 
     "httpCalls": [{
         "expectedRequest": {
-            "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+            "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
             "body": {
                 "id": "test-request-id",
                 "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json
index 63bab75b674..97214b77960 100644
--- a/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json
+++ b/adapters/lemmadigital/lemmadigitaltest/exemplary/video.json
@@ -25,7 +25,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "id": "test-request-id-video",
         "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json
index c88e0fa7861..73c1f42f44c 100644
--- a/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json
+++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/empty-seatbid-array.json
@@ -42,7 +42,7 @@
   },
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "app": {
           "bundle": "com.ld.test",
diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json
index 036d35fb345..cc0f393ad86 100644
--- a/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json
+++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/invalid-response.json
@@ -25,7 +25,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "id": "test-request-id-video",
         "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json
index 37ebea4b7be..ad25d3d2cba 100644
--- a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json
+++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-bad-request.json
@@ -25,7 +25,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "id": "test-request-id-video",
         "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json
index 7c4813df43a..e800577d5c6 100644
--- a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json
+++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-no-content.json
@@ -25,7 +25,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "id": "test-request-id-video",
         "imp": [{
diff --git a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json
index 047dc4efd83..ee13425f87a 100644
--- a/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json
+++ b/adapters/lemmadigital/lemmadigitaltest/supplemental/status-code-other-error.json
@@ -25,7 +25,7 @@
 
   "httpCalls": [{
     "expectedRequest": {
-      "uri": "https://sg.ads.lemmatechnologies.com/lemma/servad?pid=1&aid=1",
+      "uri": "https://test.lemmaurl.com/lemma/servad?src=prebid&pid=1&aid=1",
       "body": {
         "id": "test-request-id-video",
         "imp": [{
diff --git a/adapters/melozen/melozen.go b/adapters/melozen/melozen.go
new file mode 100644
index 00000000000..cb76274865b
--- /dev/null
+++ b/adapters/melozen/melozen.go
@@ -0,0 +1,185 @@
+package melozen
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"text/template"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/macros"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpointTemplate *template.Template
+}
+
+// Builder builds a new instance of the MeloZen adapter for the given bidder with the given config.
+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{
+		endpointTemplate: template,
+	}
+
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var requests []*adapters.RequestData
+	var errors []error
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	requestCopy := *request
+	for _, imp := range request.Imp {
+		// Extract Melozen Params
+		var strImpExt adapters.ExtImpBidder
+		if err := json.Unmarshal(imp.Ext, &strImpExt); err != nil {
+			errors = append(errors, err)
+			continue
+		}
+		var strImpParams openrtb_ext.ImpExtMeloZen
+		if err := json.Unmarshal(strImpExt.Bidder, &strImpParams); err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		url, err := macros.ResolveMacros(a.endpointTemplate, macros.EndpointTemplateParams{PublisherID: strImpParams.PubId})
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+		// Convert Floor into USD
+		if imp.BidFloor > 0 && imp.BidFloorCur != "" && !strings.EqualFold(imp.BidFloorCur, "USD") {
+			convertedValue, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD")
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+			imp.BidFloorCur = "USD"
+			imp.BidFloor = convertedValue
+		}
+
+		impressionsByMediaType, err := splitImpressionsByMediaType(&imp)
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		for _, impression := range impressionsByMediaType {
+			requestCopy.Imp = []openrtb2.Imp{impression}
+
+			requestJSON, err := json.Marshal(requestCopy)
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+
+			requestData := &adapters.RequestData{
+				Method:  "POST",
+				Uri:     url,
+				Body:    requestJSON,
+				Headers: headers,
+				ImpIDs:  openrtb_ext.GetImpIDs(requestCopy.Imp),
+			}
+			requests = append(requests, requestData)
+		}
+	}
+
+	return requests, errors
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(response) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
+		return nil, []error{err}
+	}
+
+	var bidReq openrtb2.BidRequest
+	if err := json.Unmarshal(requestData.Body, &bidReq); err != nil {
+		return nil, []error{err}
+	}
+
+	var bidResp openrtb2.BidResponse
+	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+		return nil, []error{err}
+	}
+
+	bidderResponse := adapters.NewBidderResponse()
+	var errors []error
+	for _, seatBid := range bidResp.SeatBid {
+		for i := range seatBid.Bid {
+			bid := &seatBid.Bid[i]
+			bidType, err := getMediaTypeForBid(*bid)
+			if err != nil {
+				errors = append(errors, err)
+				continue
+			}
+
+			bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
+				BidType: bidType,
+				Bid:     bid,
+			})
+		}
+	}
+	return bidderResponse, errors
+}
+
+func splitImpressionsByMediaType(impression *openrtb2.Imp) ([]openrtb2.Imp, error) {
+	if impression.Banner == nil && impression.Native == nil && impression.Video == nil {
+		return nil, &errortypes.BadInput{Message: "Invalid MediaType. MeloZen only supports Banner, Video and Native."}
+	}
+
+	impressions := make([]openrtb2.Imp, 0, 2)
+
+	if impression.Banner != nil {
+		impCopy := *impression
+		impCopy.Video = nil
+		impCopy.Native = nil
+		impressions = append(impressions, impCopy)
+	}
+
+	if impression.Video != nil {
+		impCopy := *impression
+		impCopy.Banner = nil
+		impCopy.Native = nil
+		impressions = append(impressions, impCopy)
+	}
+
+	if impression.Native != nil {
+		impCopy := *impression
+		impCopy.Banner = nil
+		impCopy.Video = nil
+		impressions = append(impressions, impCopy)
+	}
+
+	return impressions, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+
+	if bid.Ext != nil {
+		var bidExt openrtb_ext.ExtBid
+		err := json.Unmarshal(bid.Ext, &bidExt)
+		if err == nil && bidExt.Prebid != nil {
+			return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))
+		}
+	}
+
+	return "", &errortypes.BadServerResponse{
+		Message: fmt.Sprintf("Failed to parse bid mediatype for impression \"%s\"", bid.ImpID),
+	}
+}
diff --git a/adapters/melozen/melozen_test.go b/adapters/melozen/melozen_test.go
new file mode 100644
index 00000000000..0191ab73182
--- /dev/null
+++ b/adapters/melozen/melozen_test.go
@@ -0,0 +1,30 @@
+package melozen
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestJsonSamples(t *testing.T) {
+
+	bidder, buildErr := Builder(openrtb_ext.BidderMeloZen, config.Adapter{
+		Endpoint: "https://example.com/rtb/v2/bid?publisher_id={{.PublisherID}}",
+	}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "melozentest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+	_, buildErr := Builder(openrtb_ext.BidderMeloZen, config.Adapter{
+		Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	assert.Error(t, buildErr)
+}
diff --git a/adapters/melozen/melozentest/exemplary/app-banner.json b/adapters/melozen/melozentest/exemplary/app-banner.json
new file mode 100644
index 00000000000..6cdb82bf5ad
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-banner.json
@@ -0,0 +1,132 @@
+{
+    "mockBidRequest": {
+      "id": "web-banner",
+      "tmax": 3000,
+      "imp": [
+        {
+          "id": "banner-imp-id",
+          "ext": {
+            "bidder": {
+                "pubId": "386276e072"
+            }
+          },
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 250
+              }
+            ]
+          }
+        }
+      ],
+      "app": {
+        "bundle": "com.fake.app",
+        "publisher": {
+          "id": "42",
+          "name": "whatever.pub"
+        }
+      },
+      "device": {
+        "w": 1200,
+        "h": 900
+      }
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+          "headers": {
+            "Content-Type": ["application/json;charset=utf-8"],
+            "Accept": ["application/json"]
+          },
+          "body": {
+            "id": "web-banner",
+            "tmax": 3000,
+            "imp": [
+              {
+                "id": "banner-imp-id",
+                "ext": {
+                  "bidder": {
+                    "pubId": "386276e072"
+                  }
+                },
+                "banner": {
+                  "format": [
+                    {
+                      "w": 300,
+                      "h": 250
+                    }
+                  ]
+                }
+              }
+            ],
+            "device": {
+              "w": 1200,
+              "h": 900
+            },
+            "app": {
+              "bundle": "com.fake.app",
+              "publisher": {
+                "id": "42",
+                "name": "whatever.pub"
+              }
+            }
+          },
+          "impIDs":["banner-imp-id"]
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "web-banner",
+            "cur": "USD",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "web-banner",
+                    "impid": "banner-imp-id",
+                    "crid": "some-creative-id",
+                    "adm": "<div>Ad</div>",
+                    "price": 20,
+                    "w": 300,
+                    "h": 250,
+                    "ext": {
+                      "prebid": {
+                        "type": "banner"
+                      }
+                    }
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "web-banner",
+              "impid": "banner-imp-id",
+              "crid": "some-creative-id",
+              "adm": "<div>Ad</div>",
+              "price": 20,
+              "w": 300,
+              "h": 250,
+              "ext": {
+                "prebid": {
+                  "type": "banner"
+                }
+              }
+            },
+            "type": "banner"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/app-native.json b/adapters/melozen/melozentest/exemplary/app-native.json
new file mode 100644
index 00000000000..f93abd44bea
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-native.json
@@ -0,0 +1,100 @@
+{
+    "mockBidRequest": {
+      "id": "web-native",
+      "imp": [
+        {
+          "id": "native-imp-id",
+          "ext": {
+            "bidder": {
+                "pubId": "386276e072"
+            }
+          },
+          "native": {
+            "ver": "1.2",
+            "request": "placeholder request"
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+          "headers": {
+            "Content-Type": ["application/json;charset=utf-8"],
+            "Accept": ["application/json"]
+          },
+          "body": {
+            "id": "web-native",
+            "imp": [
+              {
+                "id": "native-imp-id",
+                "ext": {
+                  "bidder": {
+                    "pubId": "386276e072"
+                  }
+                },
+                "native": {
+                  "ver": "1.2",
+                  "request": "placeholder request"
+                }
+              }
+            ]
+          },
+          "impIDs":["native-imp-id"]
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "web-native",
+            "cur": "USD",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "web-native",
+                    "impid": "native-imp-id",
+                    "crid": "some-creative-id",
+                    "adm": "<div>Ad</div>",
+                    "price": 20,
+                    "w": 300,
+                    "h": 250,
+                    "ext": {
+                      "prebid": {
+                        "type": "native"
+                      }
+                    }
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "web-native",
+              "impid": "native-imp-id",
+              "crid": "some-creative-id",
+              "adm": "<div>Ad</div>",
+              "price": 20,
+              "w": 300,
+              "h": 250,
+              "ext": {
+                "prebid": {
+                  "type": "native"
+                }
+              }
+            },
+            "type": "native"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/app-video.json b/adapters/melozen/melozentest/exemplary/app-video.json
new file mode 100644
index 00000000000..3d913c43e44
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/app-video.json
@@ -0,0 +1,137 @@
+{
+    "mockBidRequest": {
+        "id": "app-video",
+        "tmax": 3000,
+        "imp": [
+            {
+                "id": "video-imp-id",
+                "ext": {
+                    "bidder": {
+                        "pubId": "386276e072"
+                    }
+                },
+                "video": {
+                    "w": 640,
+                    "h": 480,
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "placement": 1
+                }
+            }
+        ],
+        "app": {
+            "bundle": "com.fake.app",
+            "publisher": {
+                "id": "42",
+                "name": "whatever.pub"
+            }
+        },
+        "device": {
+            "w": 1200,
+            "h": 900
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "body": {
+                    "id": "app-video",
+                    "tmax": 3000,
+                    "imp": [
+                        {
+                            "id": "video-imp-id",
+                            "ext": {
+                                "bidder": {
+                                    "pubId": "386276e072"
+                                }
+                            },
+                            "video": {
+                                "w": 640,
+                                "h": 480,
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "placement": 1
+                            }
+                        }
+                    ],
+                    "app": {
+                        "bundle": "com.fake.app",
+                        "publisher": {
+                            "id": "42",
+                            "name": "whatever.pub"
+                        }
+                    },
+                    "device": {
+                        "w": 1200,
+                        "h": 900
+                    }
+                },
+                "impIDs": [
+                    "video-imp-id"
+                ]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "app-video",
+                    "cur": "USD",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "app-video",
+                                    "impid": "video-imp-id",
+                                    "crid": "some-creative-id",
+                                    "adm": "<VAST>TAG</VAST>",
+                                    "price": 20,
+                                    "w": 640,
+                                    "h": 480,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "video"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "app-video",
+                        "impid": "video-imp-id",
+                        "crid": "some-creative-id",
+                        "adm": "<VAST>TAG</VAST>",
+                        "price": 20,
+                        "w": 640,
+                        "h": 480,
+                        "ext": {
+                            "prebid": {
+                                "type": "video"
+                            }
+                        }
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/multi-imps.json b/adapters/melozen/melozentest/exemplary/multi-imps.json
new file mode 100644
index 00000000000..916c74cb685
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/multi-imps.json
@@ -0,0 +1,239 @@
+{
+  "mockBidRequest": {
+    "id": "web-banner",
+    "tmax": 3000,
+    "imp": [
+      {
+        "id": "banner-imp-id-1",
+        "ext": {
+          "bidder": {
+            "pubId": "386276e072"
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        }
+      },
+      {
+        "id": "banner-imp-id-2",
+        "ext": {
+          "bidder": {
+            "pubId": "386276e072"
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        }
+      }
+    ],
+    "site": {
+      "publisher": {
+        "id": "1"
+      },
+      "page": "https://some-site.com",
+      "ref": "https://some-site.com"
+    },
+    "device": {
+      "w": 1200,
+      "h": 900
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+        "headers": {
+          "Content-Type": ["application/json;charset=utf-8"],
+          "Accept": ["application/json"]
+        },
+        "body": {
+          "id": "web-banner",
+          "tmax": 3000,
+          "imp": [
+            {
+              "id": "banner-imp-id-1",
+              "ext": {
+                "bidder": {
+                  "pubId": "386276e072"
+                }
+              },
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "https://some-site.com",
+            "ref": "https://some-site.com"
+          },
+          "device": {
+            "w": 1200,
+            "h": 900
+          }
+        },
+        "impIDs":["banner-imp-id-1"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "web-banner",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "web-banner",
+                  "impid": "banner-imp-id-1",
+                  "crid": "some-creative-id",
+                  "adm": "<div>Ad</div>",
+                  "price": 20,
+                  "w": 300,
+                  "h": 250,
+                  "ext": {
+                    "prebid": {
+                      "type": "banner"
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      }
+    },
+    {
+      "expectedRequest": {
+        "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+        "headers": {
+          "Content-Type": ["application/json;charset=utf-8"],
+          "Accept": ["application/json"]
+        },
+        "body": {
+          "id": "web-banner",
+          "tmax": 3000,
+          "imp": [
+            {
+              "id": "banner-imp-id-2",
+              "ext": {
+                "bidder": {
+                  "pubId": "386276e072"
+                }
+              },
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              }
+            }
+          ],
+          "site": {
+            "publisher": {
+              "id": "1"
+            },
+            "page": "https://some-site.com",
+            "ref": "https://some-site.com"
+          },
+          "device": {
+            "w": 1200,
+            "h": 900
+          }
+        },
+        "impIDs":["banner-imp-id-2"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "web-banner",
+          "cur": "USD",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "web-banner",
+                  "impid": "banner-imp-id-2",
+                  "crid": "some-creative-id",
+                  "adm": "<div>Ad</div>",
+                  "price": 20,
+                  "w": 300,
+                  "h": 600,
+                  "ext": {
+                    "prebid": {
+                      "type": "banner"
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "web-banner",
+            "impid": "banner-imp-id-1",
+            "crid": "some-creative-id",
+            "adm": "<div>Ad</div>",
+            "price": 20,
+            "w": 300,
+            "h": 250,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    },
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "web-banner",
+            "impid": "banner-imp-id-2",
+            "crid": "some-creative-id",
+            "adm": "<div>Ad</div>",
+            "price": 20,
+            "w": 300,
+            "h": 600,
+            "ext": {
+              "prebid": {
+                "type": "banner"
+              }
+            }
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/melozen/melozentest/exemplary/web-banner.json b/adapters/melozen/melozentest/exemplary/web-banner.json
new file mode 100644
index 00000000000..0439baa1033
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/web-banner.json
@@ -0,0 +1,138 @@
+{
+    "mockBidRequest": {
+        "id": "web-banner",
+        "tmax": 3000,
+        "imp": [
+            {
+                "id": "baner-imp-id",
+                "ext": {
+                    "bidder": {
+                        "pubId": "386276e072"
+                    }
+                },
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                }
+            }
+        ],
+        "test": 0,
+        "site": {
+            "publisher": {
+                "id": "1"
+            },
+            "page": "https://some-site.com",
+            "ref": "https://some-site.com"
+        },
+        "device": {
+            "w": 1200,
+            "h": 900
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "body": {
+                    "id": "web-banner",
+                    "tmax": 3000,
+                    "imp": [
+                        {
+                            "id": "baner-imp-id",
+                            "ext": {
+                                "bidder": {
+                                    "pubId": "386276e072"
+                                }
+                            },
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            }
+                        }
+                    ],
+                    "site": {
+                        "publisher": {
+                            "id": "1"
+                        },
+                        "page": "https://some-site.com",
+                        "ref": "https://some-site.com"
+                    },
+                    "device": {
+                        "w": 1200,
+                        "h": 900
+                    }
+                },
+                "impIDs": [
+                    "baner-imp-id"
+                ]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "web-banner",
+                    "cur": "USD",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "web-banner",
+                                    "impid": "baner-imp-id",
+                                    "crid": "some-creative-id",
+                                    "adm": "<div>Ad</div>",
+                                    "price": 20,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                          "type": "banner"
+                                        }
+                                      }
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "web-banner",
+                        "impid": "baner-imp-id",
+                        "crid": "some-creative-id",
+                        "adm": "<div>Ad</div>",
+                        "price": 20,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                              "type": "banner"
+                            }
+                          }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/exemplary/web-video.json b/adapters/melozen/melozentest/exemplary/web-video.json
new file mode 100644
index 00000000000..b4f179bdc55
--- /dev/null
+++ b/adapters/melozen/melozentest/exemplary/web-video.json
@@ -0,0 +1,129 @@
+{
+    "mockBidRequest": {
+      "id": "web-video",
+      "tmax": 3000,
+      "imp": [
+        {
+          "id": "video-imp-id",
+          "ext": {
+            "bidder": {
+                "pubId": "386276e072"
+            }
+        },
+          "video": {
+            "w": 640,
+            "h": 480,
+            "mimes": ["video/mp4"],
+            "placement": 1
+          }
+        }
+      ],
+      "test": 0,
+      "site": {
+        "publisher": {
+          "id": "1"
+        },
+        "page": "https://some-site.com",
+        "ref": "https://some-site.com"
+      },
+      "device": {
+        "w": 1200,
+        "h": 900
+      }
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+          "headers": {
+            "Content-Type": ["application/json;charset=utf-8"],
+            "Accept": ["application/json"]
+          },
+          "body": {
+            "id": "web-video",
+            "tmax": 3000,
+            "imp": [
+              {
+                "id": "video-imp-id",
+                "ext": {
+                    "bidder": {
+                        "pubId": "386276e072"
+                    }
+                },
+                "video": {
+                  "w": 640,
+                  "h": 480,
+                  "mimes": ["video/mp4"],
+                  "placement": 1
+                }
+              }
+            ],
+            "site": {
+              "publisher": {
+                "id": "1"
+              },
+              "page": "https://some-site.com",
+              "ref": "https://some-site.com"
+            },
+            "device": {
+              "w": 1200,
+              "h": 900
+            }
+          },
+          "impIDs":["video-imp-id"]
+        },
+        "mockResponse": {
+          "status": 200,
+          "body": {
+            "id": "web-video",
+            "cur": "USD",
+            "seatbid": [
+              {
+                "bid": [
+                  {
+                    "id": "web-video",
+                    "impid": "video-imp-id",
+                    "crid": "some-creative-id",
+                    "adm": "<VAST>TAG</VAST>",
+                    "price": 20,
+                    "w": 640,
+                    "h": 480,
+                    "ext": {
+                      "prebid": {
+                        "type": "video"
+                      }
+                    }
+                  }
+                ]
+              }
+            ]
+          }
+        }
+      }
+    ],
+    "expectedBidResponses": [
+      {
+        "currency": "USD",
+        "bids": [
+          {
+            "bid": {
+              "id": "web-video",
+              "impid": "video-imp-id",
+              "crid": "some-creative-id",
+              "adm": "<VAST>TAG</VAST>",
+              "price": 20,
+              "w": 640,
+              "h": 480,
+              "ext": {
+                "prebid": {
+                  "type": "video"
+                }
+              }
+            },
+            "type": "video"
+          }
+        ]
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/bad-media-type-request.json b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json
new file mode 100644
index 00000000000..f6c17a70b8f
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/bad-media-type-request.json
@@ -0,0 +1,28 @@
+{
+    "mockBidRequest": {
+      "id": "unsupported-request",
+      "imp": [
+        {
+          "id": "unsupported-imp",
+          "unupported": {
+        },
+          "ext": {
+            "bidder": {
+              "pubId": "386276e072"
+            }
+          }
+        }
+      ], 
+      "site": {
+              "id": "siteID"
+          }
+    },
+  
+    "expectedMakeRequestsErrors": [
+      {
+        "value": "Invalid MediaType. MeloZen only supports Banner, Video and Native.",
+        "comparison": "literal"
+      }
+    ]
+  }
+  
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/no-fill.json b/adapters/melozen/melozentest/supplemental/no-fill.json
new file mode 100644
index 00000000000..7dd600a72b6
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/no-fill.json
@@ -0,0 +1,90 @@
+{
+    "mockBidRequest": {
+        "id": "web-banner",
+        "tmax": 3000,
+        "imp": [
+            {
+                "id": "banner-imp-id",
+                "ext": {
+                    "bidder": {
+                        "pubId": "386276e072"
+                    }
+                },
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                }
+            }
+        ],
+        "app": {
+            "bundle": "com.fake.app",
+            "publisher": {
+                "id": "42",
+                "name": "whatever.pub"
+            }
+        },
+        "device": {
+            "w": 1200,
+            "h": 900
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ]
+                },
+                "body": {
+                    "id": "web-banner",
+                    "tmax": 3000,
+                    "imp": [
+                        {
+                            "id": "banner-imp-id",
+                            "ext": {
+                                "bidder": {
+                                    "pubId": "386276e072"
+                                }
+                            },
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            }
+                        }
+                    ],
+                    "device": {
+                        "w": 1200,
+                        "h": 900
+                    },
+                    "app": {
+                        "bundle": "com.fake.app",
+                        "publisher": {
+                            "id": "42",
+                            "name": "whatever.pub"
+                        }
+                    }
+                },
+                "impIDs": [
+                    "banner-imp-id"
+                ]
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ],
+    "expectedMakeBidsErrors": []
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/response-status-400.json b/adapters/melozen/melozentest/supplemental/response-status-400.json
new file mode 100644
index 00000000000..969875b86ec
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/response-status-400.json
@@ -0,0 +1,95 @@
+{
+  "mockBidRequest": {
+    "id": "web-banner",
+    "tmax": 3000,
+    "imp": [
+      {
+        "id": "banner-imp-id",
+        "ext": {
+          "bidder": {
+            "pubId": "386276e072"
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            }
+          ]
+        }
+      }
+    ],
+    "app": {
+      "bundle": "com.fake.app",
+      "publisher": {
+        "id": "42",
+        "name": "whatever.pub"
+      }
+    },
+    "device": {
+      "w": 1200,
+      "h": 900
+    }
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+        "headers": {
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ],
+          "Accept": [
+            "application/json"
+          ]
+        },
+        "body": {
+          "id": "web-banner",
+          "tmax": 3000,
+          "imp": [
+            {
+              "id": "banner-imp-id",
+              "ext": {
+                "bidder": {
+                  "pubId": "386276e072"
+                }
+              },
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  }
+                ]
+              }
+            }
+          ],
+          "device": {
+            "w": 1200,
+            "h": 900
+          },
+          "app": {
+            "bundle": "com.fake.app",
+            "publisher": {
+              "id": "42",
+              "name": "whatever.pub"
+            }
+          }
+        },
+        "impIDs": [
+          "banner-imp-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 400
+      }
+    }
+  ],
+  "expectedMakeBidsErrors": [
+    {
+      "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+      "comparison": "literal"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/melozentest/supplemental/response-status-not-200.json b/adapters/melozen/melozentest/supplemental/response-status-not-200.json
new file mode 100644
index 00000000000..9b26ee58091
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/response-status-not-200.json
@@ -0,0 +1,84 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                      "pubId": "386276e072"
+                    }
+                  }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.test.testapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                              "pubId": "386276e072"
+                            }
+                          }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.test.testapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json
new file mode 100644
index 00000000000..b6a1c1f7268
--- /dev/null
+++ b/adapters/melozen/melozentest/supplemental/wrong-bid-ext.json
@@ -0,0 +1,85 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "video": {
+                    "w": 900,
+                    "h": 250,
+                    "mimes": [
+                        "video/x-flv",
+                        "video/mp4"
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "pubId": "386276e072"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://example.com/rtb/v2/bid?publisher_id=386276e072",
+                "headers": {
+                    "Content-Type": ["application/json;charset=utf-8"],
+                    "Accept": ["application/json"]
+                  },
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "video": {
+                                "w": 900,
+                                "h": 250,
+                                "mimes": [
+                                    "video/x-flv",
+                                    "video/mp4"
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "pubId": "386276e072"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "impIDs": [
+                    "test-imp-id"
+                ]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test-bid-id",
+                                    "impid": "another-imp-id",
+                                    "price": 3.5,
+                                    "w": 900,
+                                    "h": 250,
+                                    "ext": {}
+                                }
+                            ]
+                        }
+                    ]
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [{"currency":"USD","bids":[]}],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Failed to parse bid mediatype for impression \"another-imp-id\"",
+            "comparison": "regex"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/melozen/params_test.go b/adapters/melozen/params_test.go
new file mode 100644
index 00000000000..7e1be7f0db0
--- /dev/null
+++ b/adapters/melozen/params_test.go
@@ -0,0 +1,50 @@
+package melozen
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the JSON schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderMeloZen, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the JSON schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderMeloZen, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"pubId": "12345"}`,
+}
+
+var invalidParams = []string{
+	``,
+	`null`,
+	`true`,
+	`5`,
+	`4.2`,
+	`[]`,
+	`{}`,
+	`{"pubId": ""}`,
+	`{"pubId": 12345}`,
+}
diff --git a/adapters/missena/missena.go b/adapters/missena/missena.go
new file mode 100644
index 00000000000..93d4c2ba1cb
--- /dev/null
+++ b/adapters/missena/missena.go
@@ -0,0 +1,215 @@
+package missena
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"text/template"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type MissenaAdRequest struct {
+	RequestId        string `json:"request_id"`
+	Timeout          int    `json:"timeout"`
+	Referer          string `json:"referer"`
+	RefererCanonical string `json:"referer_canonical"`
+	GDPRConsent      string `json:"consent_string"`
+	GDPR             bool   `json:"consent_required"`
+	Placement        string `json:"placement"`
+	TestMode         string `json:"test"`
+}
+
+type MissenaBidServerResponse struct {
+	Ad        string  `json:"ad"`
+	Cpm       float64 `json:"cpm"`
+	Currency  string  `json:"currency"`
+	RequestId string  `json:"requestId"`
+}
+
+type MissenaInternalParams struct {
+	ApiKey           string
+	RequestId        string
+	Timeout          int
+	Referer          string
+	RefererCanonical string
+	GDPRConsent      string
+	GDPR             bool
+	Placement        string
+	TestMode         string
+}
+
+type MissenaAdapter struct {
+	EndpointTemplate *template.Template
+}
+
+// Builder builds a new instance of the Foo adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) makeRequest(missenaParams MissenaInternalParams, reqInfo *adapters.ExtraRequestInfo, impID string, request *openrtb2.BidRequest) (*adapters.RequestData, error) {
+	url := a.endpoint + "?t=" + missenaParams.ApiKey
+
+	missenaRequest := MissenaAdRequest{
+		RequestId:        request.ID,
+		Timeout:          2000,
+		Referer:          request.Site.Page,
+		RefererCanonical: request.Site.Domain,
+		GDPRConsent:      missenaParams.GDPRConsent,
+		GDPR:             missenaParams.GDPR,
+		Placement:        missenaParams.Placement,
+		TestMode:         missenaParams.TestMode,
+	}
+
+	body, errm := json.Marshal(missenaRequest)
+	if errm != nil {
+		return nil, errm
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+
+	if request.Device != nil {
+		headers.Add("User-Agent", request.Device.UA)
+		if request.Device.IP != "" {
+			headers.Add("X-Forwarded-For", request.Device.IP)
+		} else if request.Device.IPv6 != "" {
+			headers.Add("X-Forwarded-For", request.Device.IPv6)
+		}
+	}
+	if request.Site != nil {
+		headers.Add("Referer", request.Site.Page)
+	}
+
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     url,
+		Headers: headers,
+		Body:    body,
+		ImpIDs:  []string{impID},
+	}, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+	var httpRequests []*adapters.RequestData
+	var errors []error
+	gdprApplies, consentString := readGDPR(request)
+
+	missenaInternalParams := MissenaInternalParams{
+		GDPR:        gdprApplies,
+		GDPRConsent: consentString,
+	}
+
+	for _, imp := range request.Imp {
+		var bidderExt adapters.ExtImpBidder
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errors = append(errors, &errortypes.BadInput{
+				Message: "Error parsing bidderExt object",
+			})
+			continue
+		}
+
+		var missenaExt openrtb_ext.ExtImpMissena
+		if err := json.Unmarshal(bidderExt.Bidder, &missenaExt); err != nil {
+			errors = append(errors, &errortypes.BadInput{
+				Message: "Error parsing missenaExt parameters",
+			})
+			continue
+		}
+
+		missenaInternalParams.ApiKey = missenaExt.ApiKey
+		missenaInternalParams.Placement = missenaExt.Placement
+		missenaInternalParams.TestMode = missenaExt.TestMode
+
+		newHttpRequest, err := a.makeRequest(missenaInternalParams, requestInfo, imp.ID, request)
+		if err != nil {
+			errors = append(errors, err)
+			continue
+		}
+
+		httpRequests = append(httpRequests, newHttpRequest)
+
+		break
+	}
+
+	return httpRequests, errors
+}
+
+func readGDPR(request *openrtb2.BidRequest) (bool, string) {
+	consentString := ""
+	if request.User != nil {
+		var extUser openrtb_ext.ExtUser
+		if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
+			consentString = extUser.Consent
+		}
+	}
+	gdprApplies := false
+	var extRegs openrtb_ext.ExtRegs
+	if request.Regs != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil {
+			if extRegs.GDPR != nil {
+				gdprApplies = (*extRegs.GDPR == 1)
+			}
+		}
+	}
+	return gdprApplies, consentString
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if responseData.StatusCode == http.StatusNoContent {
+		return nil, nil
+	}
+
+	if responseData.StatusCode == http.StatusBadRequest {
+		err := &errortypes.BadInput{
+			Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+		}
+		return nil, []error{err}
+	}
+
+	if responseData.StatusCode != http.StatusOK {
+		err := &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
+		}
+		return nil, []error{err}
+	}
+
+	var missenaResponse MissenaBidServerResponse
+	if err := json.Unmarshal(responseData.Body, &missenaResponse); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
+	bidResponse.Currency = missenaResponse.Currency
+
+	responseBid := &openrtb2.Bid{
+		ID:    request.ID,
+		Price: float64(missenaResponse.Cpm),
+		ImpID: request.Imp[0].ID,
+		AdM:   missenaResponse.Ad,
+		CrID:  missenaResponse.RequestId,
+	}
+
+	b := &adapters.TypedBid{
+		Bid:     responseBid,
+		BidType: openrtb_ext.BidTypeBanner,
+	}
+
+	bidResponse.Bids = append(bidResponse.Bids, b)
+
+	return bidResponse, nil
+}
diff --git a/adapters/missena/missena_test.go b/adapters/missena/missena_test.go
new file mode 100644
index 00000000000..2b13bf085db
--- /dev/null
+++ b/adapters/missena/missena_test.go
@@ -0,0 +1,21 @@
+package missena
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderMissena, config.Adapter{
+		Endpoint: "http://example.com/"},
+		config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "missenatest", bidder)
+}
diff --git a/adapters/missena/missenatest/exemplary/multiple-imps.json b/adapters/missena/missenatest/exemplary/multiple-imps.json
new file mode 100644
index 00000000000..5b83f19ccd0
--- /dev/null
+++ b/adapters/missena/missenatest/exemplary/multiple-imps.json
@@ -0,0 +1,129 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id-1",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement-1",
+                        "test": "1"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id-2",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement-2",
+                        "test": "1"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id-3",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": "abc"
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "123.123.123.123"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement-1",
+                    "test": "1"
+                },
+                "impIDs":["test-imp-id-1"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "ad": "<div>test ad</div>",
+                    "cpm": 1.5,
+                    "currency": "EUR",
+                    "requestId": "test-request-id"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-request-id",
+                        "impid": "test-imp-id-1",
+                        "price": 1.5,
+                        "adm": "<div>test ad</div>",
+                        "crid": "test-request-id"
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/exemplary/simple-banner-ipv6.json b/adapters/missena/missenatest/exemplary/simple-banner-ipv6.json
new file mode 100644
index 00000000000..ea240f82e09
--- /dev/null
+++ b/adapters/missena/missenatest/exemplary/simple-banner-ipv6.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ipv6": "2001:0000:130F:0000:0000:09C0:876A:130B",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement",
+                        "test": "1"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "2001:0000:130F:0000:0000:09C0:876A:130B"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement",
+                    "test": "1"
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "ad": "<div>test ad</div>",
+                    "cpm": 1.5,
+                    "currency": "EUR",
+                    "requestId": "test-request-id"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-request-id",
+                        "impid": "test-imp-id",
+                        "price": 1.5,
+                        "adm": "<div>test ad</div>",
+                        "crid": "test-request-id"
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/exemplary/simple-banner.json b/adapters/missena/missenatest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..74ff3abfd57
--- /dev/null
+++ b/adapters/missena/missenatest/exemplary/simple-banner.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement",
+                        "test": "1"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "123.123.123.123"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement",
+                    "test": "1"
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "ad": "<div>test ad</div>",
+                    "cpm": 1.5,
+                    "currency": "EUR",
+                    "requestId": "test-request-id"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-request-id",
+                        "impid": "test-imp-id",
+                        "price": 1.5,
+                        "adm": "<div>test ad</div>",
+                        "crid": "test-request-id"
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/exemplary/valid-imp-error-imp.json b/adapters/missena/missenatest/exemplary/valid-imp-error-imp.json
new file mode 100644
index 00000000000..61be3f78c4c
--- /dev/null
+++ b/adapters/missena/missenatest/exemplary/valid-imp-error-imp.json
@@ -0,0 +1,129 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id-1",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement-1",
+                        "test": "1"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id-2",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement-2",
+                        "test": "1"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id-3",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": "abc"
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "123.123.123.123"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement-1",
+                    "test": "1"
+                },
+                "impIDs": ["test-imp-id-1"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "ad": "<div>test ad</div>",
+                    "cpm": 1.5,
+                    "currency": "EUR",
+                    "requestId": "test-request-id"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "EUR",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test-request-id",
+                        "impid": "test-imp-id-1",
+                        "price": 1.5,
+                        "adm": "<div>test ad</div>",
+                        "crid": "test-request-id"
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/supplemental/error-ext-bidder.json b/adapters/missena/missenatest/supplemental/error-ext-bidder.json
new file mode 100644
index 00000000000..fdc08f4704b
--- /dev/null
+++ b/adapters/missena/missenatest/supplemental/error-ext-bidder.json
@@ -0,0 +1,25 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://publisher.com/url"
+        },
+        "user": {
+            "buyeruid": "1"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": {
+                    "bidder": "abc"
+                }
+            }
+        ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "Error parsing missenaExt parameters",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/supplemental/error-imp-ext.json b/adapters/missena/missenatest/supplemental/error-imp-ext.json
new file mode 100644
index 00000000000..3905efa6bab
--- /dev/null
+++ b/adapters/missena/missenatest/supplemental/error-imp-ext.json
@@ -0,0 +1,23 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://publisher.com/url"
+        },
+        "user": {
+            "buyeruid": "1"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": "error"
+            }
+        ]
+    },
+    "expectedMakeRequestsErrors": [
+        {
+            "value": "Error parsing bidderExt object",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/supplemental/status-204.json b/adapters/missena/missenatest/supplemental/status-204.json
new file mode 100644
index 00000000000..59070ab4ecb
--- /dev/null
+++ b/adapters/missena/missenatest/supplemental/status-204.json
@@ -0,0 +1,83 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement",
+                        "test": "1"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "123.123.123.123"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement",
+                    "test": "1"
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/missena/missenatest/supplemental/status-400.json b/adapters/missena/missenatest/supplemental/status-400.json
new file mode 100644
index 00000000000..23a153208e3
--- /dev/null
+++ b/adapters/missena/missenatest/supplemental/status-400.json
@@ -0,0 +1,89 @@
+{
+    "mockBidRequest": {
+      "id": "test-request-id",
+      "tmax": 500,
+      "at": 1,
+      "cur": [
+        "EUR"
+      ],
+      "regs": {
+        "ext": {
+          "gdpr": 1
+        }
+      },
+      "user": {
+        "ext": {
+          "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+        }
+      },
+      "device": {
+        "ip": "123.123.123.123",
+        "ua": "test-user-agent"
+      },
+      "site": {
+        "page": "https://example.com/page",
+        "domain": "example.com"
+      },
+      "imp": [
+        {
+          "id": "test-imp-id",
+          "banner": {
+            "h": 50,
+            "w": 320
+          },
+          "ext": {
+            "bidder": {
+              "apiKey": "test-api-key",
+              "placement": "test-placement",
+              "test": "1"
+            }
+          }
+        }
+      ]
+    },
+    "httpCalls": [
+      {
+        "expectedRequest": {
+          "uri": "http://example.com/?t=test-api-key",
+          "headers": {
+            "Content-Type": [
+              "application/json;charset=utf-8"
+            ],
+            "Accept": [
+              "application/json"
+            ],
+            "User-Agent": [
+              "test-user-agent"
+            ],
+            "X-Forwarded-For": [
+              "123.123.123.123"
+            ],
+            "Referer": [
+              "https://example.com/page"
+            ]
+          },
+          "body": {
+            "request_id": "test-request-id",
+            "timeout": 2000,
+            "referer": "https://example.com/page",
+            "referer_canonical": "example.com",
+            "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+            "consent_required": true,
+            "placement": "test-placement",
+            "test": "1"
+          },
+          "impIDs":["test-imp-id"]
+        },
+        "mockResponse": {
+          "status": 400,
+          "body": "Bad request from publisher."
+        }
+      }
+    ],
+    "expectedMakeBidsErrors": [
+      {
+        "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
+        "comparison": "literal"
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/adapters/missena/missenatest/supplemental/status-not-200.json b/adapters/missena/missenatest/supplemental/status-not-200.json
new file mode 100644
index 00000000000..8c913791fc3
--- /dev/null
+++ b/adapters/missena/missenatest/supplemental/status-not-200.json
@@ -0,0 +1,89 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "tmax": 500,
+        "at": 1,
+        "cur": [
+            "EUR"
+        ],
+        "regs": {
+            "ext": {
+                "gdpr": 1
+            }
+        },
+        "user": {
+            "ext": {
+                "consent": "CO-X2XiO_eyUoAsAxBFRBECsA"
+            }
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "test-user-agent"
+        },
+        "site": {
+            "page": "https://example.com/page",
+            "domain": "example.com"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "h": 50,
+                    "w": 320
+                },
+                "ext": {
+                    "bidder": {
+                        "apiKey": "test-api-key",
+                        "placement": "test-placement",
+                        "test": "1"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://example.com/?t=test-api-key",
+                "headers": {
+                    "Content-Type": [
+                        "application/json;charset=utf-8"
+                    ],
+                    "Accept": [
+                        "application/json"
+                    ],
+                    "User-Agent": [
+                        "test-user-agent"
+                    ],
+                    "X-Forwarded-For": [
+                        "123.123.123.123"
+                    ],
+                    "Referer": [
+                        "https://example.com/page"
+                    ]
+                },
+                "body": {
+                    "request_id": "test-request-id",
+                    "timeout": 2000,
+                    "referer": "https://example.com/page",
+                    "referer_canonical": "example.com",
+                    "consent_string": "CO-X2XiO_eyUoAsAxBFRBECsA",
+                    "consent_required": true,
+                    "placement": "test-placement",
+                    "test": "1"
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 404,
+                "body": {}
+            }
+        }
+    ],
+    "expectedMakeBidsErrors": [
+        {
+            "value": "Unexpected status code: 404. Run with request.debug = 1 for more info.",
+            "comparison": "literal"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/missena/params_test.go b/adapters/missena/params_test.go
new file mode 100644
index 00000000000..e76b80b694f
--- /dev/null
+++ b/adapters/missena/params_test.go
@@ -0,0 +1,50 @@
+package missena
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderMissena, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderMissena, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"apiKey": "PA-123456"}`,
+	`{"apiKey": "PA-123456", "placement": "sticky"}`,
+	`{"apiKey": "PA-123456", "test": "native"}`,
+}
+
+var invalidParams = []string{
+	`{"apiKey": ""}`,
+	`{"apiKey": 42}`,
+	`{"placement": 111}`,
+	`{"placement": "sticky"}`,
+	`{"apiKey": "PA-123456", "placement": 111}`,
+	`{"test": "native"}`,
+	`{"apiKey": "PA-123456", "test": 111}`,
+}
diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go
index c29be0baa81..aa88c4a0a3e 100644
--- a/adapters/openx/openx.go
+++ b/adapters/openx/openx.go
@@ -169,7 +169,7 @@ func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error {
 
 	if imp.Video != nil {
 		videoCopy := *imp.Video
-		if bidderExt.Prebid != nil && bidderExt.Prebid.IsRewardedInventory != nil && *bidderExt.Prebid.IsRewardedInventory == 1 {
+		if imp.Rwdd == 1 {
 			videoCopy.Ext = json.RawMessage(`{"rewarded":1}`)
 		} else {
 			videoCopy.Ext = nil
diff --git a/adapters/openx/openxtest/exemplary/video-rewarded.json b/adapters/openx/openxtest/exemplary/video-rewarded.json
index b16a92f23ac..5e853948a63 100644
--- a/adapters/openx/openxtest/exemplary/video-rewarded.json
+++ b/adapters/openx/openxtest/exemplary/video-rewarded.json
@@ -14,13 +14,11 @@
           }
         },
         "instl": 1,
+        "rwdd": 1,
         "ext": {
           "bidder": {
             "unit": "539439964",
             "delDomain": "se-demo-d.openx.net"
-          },
-          "prebid": {
-            "is_rewarded_inventory": 1
           }
         }
       }
@@ -46,7 +44,8 @@
                 }
               },
               "tagid": "539439964",
-              "instl": 1
+              "instl": 1,
+              "rwdd": 1
             }
           ],
           "ext": {
diff --git a/adapters/oraki/oraki.go b/adapters/oraki/oraki.go
new file mode 100644
index 00000000000..0e29aa9bd5e
--- /dev/null
+++ b/adapters/oraki/oraki.go
@@ -0,0 +1,152 @@
+package oraki
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type reqBodyExt struct {
+	OrakiBidderExt reqBodyExtBidder `json:"bidder"`
+}
+
+type reqBodyExtBidder struct {
+	Type        string `json:"type"`
+	PlacementID string `json:"placementId,omitempty"`
+	EndpointID  string `json:"endpointId,omitempty"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+	var adapterRequests []*adapters.RequestData
+
+	reqCopy := *request
+	for _, imp := range request.Imp {
+		reqCopy.Imp = []openrtb2.Imp{imp}
+
+		var bidderExt adapters.ExtImpBidder
+		var orakiExt openrtb_ext.ImpExtOraki
+
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if err := json.Unmarshal(bidderExt.Bidder, &orakiExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		impExt := reqBodyExt{OrakiBidderExt: reqBodyExtBidder{}}
+
+		if orakiExt.PlacementID != "" {
+			impExt.OrakiBidderExt.PlacementID = orakiExt.PlacementID
+			impExt.OrakiBidderExt.Type = "publisher"
+		} else if orakiExt.EndpointID != "" {
+			impExt.OrakiBidderExt.EndpointID = orakiExt.EndpointID
+			impExt.OrakiBidderExt.Type = "network"
+		}
+
+		finalyImpExt, err := json.Marshal(impExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		reqCopy.Imp[0].Ext = finalyImpExt
+
+		adapterReq, err := a.makeRequest(&reqCopy)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		adapterRequests = append(adapterRequests, adapterReq)
+	}
+
+	return adapterRequests, nil
+}
+
+func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJSON,
+		Headers: headers,
+		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
+	}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	if len(response.Cur) != 0 {
+		bidResponse.Currency = response.Cur
+	}
+
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bid := seatBid.Bid[i]
+			bidType, err := getBidType(bid)
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
+
+func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	// determinate media type by bid response field mtype
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	}
+
+	return "", fmt.Errorf("could not define media type for impression: %s", bid.ImpID)
+}
diff --git a/adapters/oraki/oraki_test.go b/adapters/oraki/oraki_test.go
new file mode 100644
index 00000000000..f801e9816ed
--- /dev/null
+++ b/adapters/oraki/oraki_test.go
@@ -0,0 +1,20 @@
+package oraki
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderOraki, config.Adapter{
+		Endpoint: "https://fake.test.io/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "orakitest", bidder)
+}
diff --git a/adapters/oraki/orakitest/exemplary/endpointId.json b/adapters/oraki/orakitest/exemplary/endpointId.json
new file mode 100644
index 00000000000..fb211d4e765
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/endpointId.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/multi-format.json b/adapters/oraki/orakitest/exemplary/multi-format.json
new file mode 100644
index 00000000000..cb1c6ffd9c6
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/multi-format.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/multi-imp.json b/adapters/oraki/orakitest/exemplary/multi-imp.json
new file mode 100644
index 00000000000..43bbf483960
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/multi-imp.json
@@ -0,0 +1,253 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id1",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id2",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id1",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id1"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id1",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        },
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id2",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id2"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id2",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id1",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        },
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id2",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/simple-banner.json b/adapters/oraki/orakitest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..ee6352c92c9
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/simple-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/simple-native.json b/adapters/oraki/orakitest/exemplary/simple-native.json
new file mode 100644
index 00000000000..f517a686ccd
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/simple-native.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "native": {
+                    "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                    "ver": "1.1"
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "native": {
+                                "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                                "ver": "1.1"
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 4,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "native"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 4,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "native"
+                            }
+                        }
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/simple-video.json b/adapters/oraki/orakitest/exemplary/simple-video.json
new file mode 100644
index 00000000000..d51d8b59f75
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/simple-video.json
@@ -0,0 +1,131 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    },
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 2,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "video"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 2,
+                        "ext": {
+                            "prebid": {
+                                "type": "video"
+                            }
+                        }
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/exemplary/simple-web-banner.json b/adapters/oraki/orakitest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000000..7b890db4e9f
--- /dev/null
+++ b/adapters/oraki/orakitest/exemplary/simple-web-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "id": "1",
+            "domain": "test.com"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "Ubuntu"
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://fake.test.io/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "id": "1",
+                        "domain": "test.com"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "Ubuntu"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 468,
+                                    "h": 60,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "oraki"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 468,
+                        "h": 60,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/oraki/orakitest/supplemental/bad_media_type.json b/adapters/oraki/orakitest/supplemental/bad_media_type.json
new file mode 100644
index 00000000000..0708ed4ae31
--- /dev/null
+++ b/adapters/oraki/orakitest/supplemental/bad_media_type.json
@@ -0,0 +1,83 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://fake.test.io/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": {
+                "id": "test-request-id",
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "test_bid_id",
+                                "impid": "test-imp-id",
+                                "price": 0.27543,
+                                "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://fake.test.io/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                "cid": "test_cid",
+                                "crid": "test_crid",
+                                "dealid": "test_dealid",
+                                "w": 300,
+                                "h": 250,
+                                "ext": {}
+                            }
+                        ],
+                        "seat": "oraki"
+                    }
+                ],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "could not define media type for impression: test-imp-id",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/oraki/orakitest/supplemental/bad_response.json b/adapters/oraki/orakitest/supplemental/bad_response.json
new file mode 100644
index 00000000000..d8029b5ed6b
--- /dev/null
+++ b/adapters/oraki/orakitest/supplemental/bad_response.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://fake.test.io/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": ""
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/oraki/orakitest/supplemental/status-204.json b/adapters/oraki/orakitest/supplemental/status-204.json
new file mode 100644
index 00000000000..f72a2eb0607
--- /dev/null
+++ b/adapters/oraki/orakitest/supplemental/status-204.json
@@ -0,0 +1,80 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://fake.test.io/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 204,
+			"body": {}
+		}
+    }],
+    "expectedBidResponses": []
+}
diff --git a/adapters/oraki/orakitest/supplemental/status-not-200.json b/adapters/oraki/orakitest/supplemental/status-not-200.json
new file mode 100644
index 00000000000..218aa632618
--- /dev/null
+++ b/adapters/oraki/orakitest/supplemental/status-not-200.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://fake.test.io/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/oraki/params_test.go b/adapters/oraki/params_test.go
new file mode 100644
index 00000000000..15981de5fa7
--- /dev/null
+++ b/adapters/oraki/params_test.go
@@ -0,0 +1,47 @@
+package oraki
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderOraki, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderOraki, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "test"}`,
+	`{"placementId": "1"}`,
+	`{"endpointId": "test"}`,
+	`{"endpointId": "1"}`,
+}
+
+var invalidParams = []string{
+	`{"placementId": 42}`,
+	`{"endpointId": 42}`,
+	`{"placementId": "1", "endpointId": "1"}`,
+}
diff --git a/adapters/ownadx/ownadx.go b/adapters/ownadx/ownadx.go
index 5678808b99b..7c858cd6d85 100644
--- a/adapters/ownadx/ownadx.go
+++ b/adapters/ownadx/ownadx.go
@@ -55,9 +55,9 @@ func createBidRequest(rtbBidRequest *openrtb2.BidRequest, imps []openrtb2.Imp) *
 }
 func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpOwnAdx) (string, error) {
 	endpointParams := macros.EndpointTemplateParams{
-		ZoneID:    params.SspId,
-		AccountID: params.SeatId,
-		SourceId:  params.TokenId,
+		SspID:   params.SspId, // Macro
+		SeatID:  params.SeatId,
+		TokenID: params.TokenId,
 	}
 	return macros.ResolveMacros(adapter.endpoint, endpointParams)
 }
@@ -123,6 +123,7 @@ func groupImpsByExt(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpOwnAdx][]openrtb
 }
 
 func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+
 	if response.StatusCode == http.StatusNoContent {
 		return nil, nil
 	}
@@ -158,6 +159,7 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR
 
 	seatBid := bidResp.SeatBid[0]
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
+
 	if len(seatBid.Bid) == 0 {
 		return nil, []error{
 			&errortypes.BadServerResponse{
@@ -168,7 +170,6 @@ func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR
 	for i := 0; i < len(seatBid.Bid); i++ {
 		var bidType openrtb_ext.BidType
 		bid := seatBid.Bid[i]
-
 		bidType, err := getMediaType(bid)
 		if err != nil {
 			return nil, []error{&errortypes.BadServerResponse{
diff --git a/adapters/ownadx/ownadx_test.go b/adapters/ownadx/ownadx_test.go
index 07dc928b9b0..5995cdd10a7 100644
--- a/adapters/ownadx/ownadx_test.go
+++ b/adapters/ownadx/ownadx_test.go
@@ -11,7 +11,7 @@ import (
 
 func TestJsonSamples(t *testing.T) {
 	bidder, buildErr := Builder(openrtb_ext.BidderOwnAdx, config.Adapter{
-		Endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.AccountID}}/{{.ZoneID}}?token={{.SourceId}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+		Endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.SeatID}}/{{.SspID}}?token={{.TokenID}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
 
 	assert.NoError(t, buildErr)
 	adapterstest.RunJSONBidderTest(t, "ownadxtest", bidder)
diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json
index 9b1c03b91dd..7aa09d03ce0 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/video.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/video.json
@@ -176,7 +176,8 @@
             },
             "type": "video",
             "video" :{
-              "duration" : 5
+              "duration" : 5,
+              "primary_category": ""
             }
           }
         ]
diff --git a/adapters/pubrise/params_test.go b/adapters/pubrise/params_test.go
new file mode 100644
index 00000000000..df5d38fd02e
--- /dev/null
+++ b/adapters/pubrise/params_test.go
@@ -0,0 +1,47 @@
+package pubrise
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestValidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range validParams {
+		if err := validator.Validate(openrtb_ext.BidderPubrise, json.RawMessage(p)); err != nil {
+			t.Errorf("Schema rejected valid params: %s", p)
+		}
+	}
+}
+
+func TestInvalidParams(t *testing.T) {
+	validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to fetch the json schema. %v", err)
+	}
+
+	for _, p := range invalidParams {
+		if err := validator.Validate(openrtb_ext.BidderPubrise, json.RawMessage(p)); err == nil {
+			t.Errorf("Schema allowed invalid params: %s", p)
+		}
+	}
+}
+
+var validParams = []string{
+	`{"placementId": "test"}`,
+	`{"placementId": "1"}`,
+	`{"endpointId": "test"}`,
+	`{"endpointId": "1"}`,
+}
+
+var invalidParams = []string{
+	`{"placementId": 42}`,
+	`{"endpointId": 42}`,
+	`{"placementId": "1", "endpointId": "1"}`,
+}
diff --git a/adapters/pubrise/pubrise.go b/adapters/pubrise/pubrise.go
new file mode 100644
index 00000000000..9d71f2e1439
--- /dev/null
+++ b/adapters/pubrise/pubrise.go
@@ -0,0 +1,159 @@
+package pubrise
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/adapters"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type adapter struct {
+	endpoint string
+}
+
+type reqBodyExt struct {
+	PubriseBidderExt reqBodyExtBidder `json:"bidder"`
+}
+
+type reqBodyExtBidder struct {
+	Type        string `json:"type"`
+	PlacementID string `json:"placementId,omitempty"`
+	EndpointID  string `json:"endpointId,omitempty"`
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+	bidder := &adapter{
+		endpoint: config.Endpoint,
+	}
+	return bidder, nil
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+	var errs []error
+	var adapterRequests []*adapters.RequestData
+
+	reqCopy := *request
+	for _, imp := range request.Imp {
+		reqCopy.Imp = []openrtb2.Imp{imp}
+
+		var bidderExt adapters.ExtImpBidder
+		var pubriseExt openrtb_ext.ImpExtPubrise
+
+		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		if err := json.Unmarshal(bidderExt.Bidder, &pubriseExt); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		impExt := reqBodyExt{PubriseBidderExt: reqBodyExtBidder{}}
+
+		if pubriseExt.PlacementID != "" {
+			impExt.PubriseBidderExt.PlacementID = pubriseExt.PlacementID
+			impExt.PubriseBidderExt.Type = "publisher"
+		} else if pubriseExt.EndpointID != "" {
+			impExt.PubriseBidderExt.EndpointID = pubriseExt.EndpointID
+			impExt.PubriseBidderExt.Type = "network"
+		}
+
+		finalyImpExt, err := json.Marshal(impExt)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		reqCopy.Imp[0].Ext = finalyImpExt
+
+		adapterReq, err := a.makeRequest(&reqCopy)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		if adapterReq != nil {
+			adapterRequests = append(adapterRequests, adapterReq)
+		}
+	}
+
+	if len(adapterRequests) == 0 {
+		errs = append(errs, errors.New("found no valid impressions"))
+		return nil, errs
+	}
+
+	return adapterRequests, nil
+}
+
+func (a *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) {
+	reqJSON, err := json.Marshal(request)
+	if err != nil {
+		return nil, err
+	}
+
+	headers := http.Header{}
+	headers.Add("Content-Type", "application/json;charset=utf-8")
+	headers.Add("Accept", "application/json")
+	return &adapters.RequestData{
+		Method:  "POST",
+		Uri:     a.endpoint,
+		Body:    reqJSON,
+		Headers: headers,
+		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
+	}, nil
+}
+
+func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+	if adapters.IsResponseStatusCodeNoContent(responseData) {
+		return nil, nil
+	}
+
+	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
+		return nil, []error{err}
+	}
+
+	var response openrtb2.BidResponse
+	if err := json.Unmarshal(responseData.Body, &response); err != nil {
+		return nil, []error{err}
+	}
+
+	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
+	if len(response.Cur) != 0 {
+		bidResponse.Currency = response.Cur
+	}
+
+	for _, seatBid := range response.SeatBid {
+		for i := range seatBid.Bid {
+			bidType, err := getBidType(seatBid.Bid[i])
+			if err != nil {
+				return nil, []error{err}
+			}
+
+			b := &adapters.TypedBid{
+				Bid:     &seatBid.Bid[i],
+				BidType: bidType,
+			}
+			bidResponse.Bids = append(bidResponse.Bids, b)
+		}
+	}
+	return bidResponse, nil
+}
+
+func getBidType(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+	// determinate media type by bid response field mtype
+	switch bid.MType {
+	case openrtb2.MarkupBanner:
+		return openrtb_ext.BidTypeBanner, nil
+	case openrtb2.MarkupVideo:
+		return openrtb_ext.BidTypeVideo, nil
+	case openrtb2.MarkupNative:
+		return openrtb_ext.BidTypeNative, nil
+	}
+
+	return "", fmt.Errorf("could not define media type for impression: %s", bid.ImpID)
+}
diff --git a/adapters/pubrise/pubrise_test.go b/adapters/pubrise/pubrise_test.go
new file mode 100644
index 00000000000..a50878c339e
--- /dev/null
+++ b/adapters/pubrise/pubrise_test.go
@@ -0,0 +1,20 @@
+package pubrise
+
+import (
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/adapters/adapterstest"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+	bidder, buildErr := Builder(openrtb_ext.BidderEmtv, config.Adapter{
+		Endpoint: "https://backend.pubrise.ai/pserver"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+	if buildErr != nil {
+		t.Fatalf("Builder returned unexpected error %v", buildErr)
+	}
+
+	adapterstest.RunJSONBidderTest(t, "pubrisetest", bidder)
+}
diff --git a/adapters/pubrise/pubrisetest/exemplary/endpointId.json b/adapters/pubrise/pubrisetest/exemplary/endpointId.json
new file mode 100644
index 00000000000..3766c7a3ef4
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/endpointId.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/multi-format.json b/adapters/pubrise/pubrisetest/exemplary/multi-format.json
new file mode 100644
index 00000000000..9e5c2ef2c57
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/multi-format.json
@@ -0,0 +1,105 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 204
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/multi-imp.json b/adapters/pubrise/pubrisetest/exemplary/multi-imp.json
new file mode 100644
index 00000000000..d922113a512
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/multi-imp.json
@@ -0,0 +1,253 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            },
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "endpointId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        },
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "endpointId": "test",
+                                    "type": "network"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        },
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/simple-banner.json b/adapters/pubrise/pubrisetest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..6669c460ea1
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/simple-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/simple-native.json b/adapters/pubrise/pubrisetest/exemplary/simple-native.json
new file mode 100644
index 00000000000..e535c9ce5f9
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/simple-native.json
@@ -0,0 +1,120 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "native": {
+                    "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                    "ver": "1.1"
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "native": {
+                                "request": "{\"ver\":\"1.1\",\"layout\":1,\"adunit\":2,\"plcmtcnt\":6,\"plcmttype\":4,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":75}},{\"id\":2,\"required\":1,\"img\":{\"wmin\":492,\"hmin\":328,\"type\":3,\"mimes\":[\"image/jpeg\",\"image/jpg\",\"image/png\"]}},{\"id\":4,\"required\":0,\"data\":{\"type\":6}},{\"id\":5,\"required\":0,\"data\":{\"type\":7}},{\"id\":6,\"required\":0,\"data\":{\"type\":1,\"len\":20}}]}",
+                                "ver": "1.1"
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 4,
+                                    "w": 300,
+                                    "h": 250,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "native"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 4,
+                        "w": 300,
+                        "h": 250,
+                        "ext": {
+                            "prebid": {
+                                "type": "native"
+                            }
+                        }
+                    },
+                    "type": "native"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/simple-video.json b/adapters/pubrise/pubrisetest/exemplary/simple-video.json
new file mode 100644
index 00000000000..047b2bc7d99
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/simple-video.json
@@ -0,0 +1,131 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "iPad"
+        },
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "video": {
+                    "mimes": [
+                        "video/mp4"
+                    ],
+                    "protocols": [
+                        2,
+                        5
+                    ],
+                    "w": 1024,
+                    "h": 576
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "iPad"
+                    },
+                    "app": {
+                        "id": "1",
+                        "bundle": "com.wls.testwlsapplication"
+                    },
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "video": {
+                                "mimes": [
+                                    "video/mp4"
+                                ],
+                                "protocols": [
+                                    2,
+                                    5
+                                ],
+                                "w": 1024,
+                                "h": 576
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 2,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "video"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "currency": "USD",
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<VAST version=\"3.0\"><Ad id=\"20001\" sequence=\"1\"><InLine><AdSystem version=\"4.0\"><![CDATA[iabtechlab]]></AdSystem><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><Impression><![CDATA[]]></Impression><Creatives><Creative id=\"5480\" sequence=\"1\"><Linear><Duration>00:01:00</Duration><MediaFiles><MediaFile id=\"5246\" delivery=\"progressive\" type=\"video/mp4\" bitrate=\"600\" width=\"640\" height=\"360\" minBitrate=\"500\" maxBitrate=\"700\" scalable=\"1\" maintainAspectRatio=\"1\" codec=\"0\"><![CDATA[https://s0.2mdn.net/4253510/google_ddm_animation_480P.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickThrough id=\"blog\"><![CDATA[https://example.com]]></ClickThrough></VideoClicks></Linear></Creative></Creatives></InLine></Ad></VAST>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 2,
+                        "ext": {
+                            "prebid": {
+                                "type": "video"
+                            }
+                        }
+                    },
+                    "type": "video"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/exemplary/simple-web-banner.json b/adapters/pubrise/pubrisetest/exemplary/simple-web-banner.json
new file mode 100644
index 00000000000..ef9b8080ae7
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/exemplary/simple-web-banner.json
@@ -0,0 +1,136 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "tagid": "test",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "site": {
+            "id": "1",
+            "domain": "test.com"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ua": "Ubuntu"
+        }
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "https://backend.pubrise.ai/pserver",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "test-imp-id",
+                            "tagid": "test",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": "test",
+                                    "type": "publisher"
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "id": "1",
+                        "domain": "test.com"
+                    },
+                    "device": {
+                        "ip": "123.123.123.123",
+                        "ua": "Ubuntu"
+                    }
+                },
+                "impIDs":["test-imp-id"]
+            },
+            "mockResponse": {
+                "status": 200,
+                "body": {
+                    "id": "test-request-id",
+                    "seatbid": [
+                        {
+                            "bid": [
+                                {
+                                    "id": "test_bid_id",
+                                    "impid": "test-imp-id",
+                                    "price": 0.27543,
+                                    "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                                    "cid": "test_cid",
+                                    "crid": "test_crid",
+                                    "dealid": "test_dealid",
+                                    "mtype": 1,
+                                    "w": 468,
+                                    "h": 60,
+                                    "ext": {
+                                        "prebid": {
+                                            "type": "banner"
+                                        }
+                                    }
+                                }
+                            ],
+                            "seat": "pubrise"
+                        }
+                    ],
+                    "cur": "USD"
+                }
+            }
+        }
+    ],
+    "expectedBidResponses": [
+        {
+            "bids": [
+                {
+                    "bid": {
+                        "id": "test_bid_id",
+                        "impid": "test-imp-id",
+                        "price": 0.27543,
+                        "adm": "<iframe id=\"adm-banner-1\" width=\"468\" height=\"60\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=bc2d316f39931a07d9a8dd249bf85fc0\"></iframe>",
+                        "cid": "test_cid",
+                        "crid": "test_crid",
+                        "dealid": "test_dealid",
+                        "mtype": 1,
+                        "w": 468,
+                        "h": 60,
+                        "ext": {
+                            "prebid": {
+                                "type": "banner"
+                            }
+                        }
+                    },
+                    "type": "banner"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/adapters/pubrise/pubrisetest/supplemental/bad_media_type.json b/adapters/pubrise/pubrisetest/supplemental/bad_media_type.json
new file mode 100644
index 00000000000..2b6165345ac
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/supplemental/bad_media_type.json
@@ -0,0 +1,83 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://backend.pubrise.ai/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": {
+                "id": "test-request-id",
+                "seatbid": [
+                    {
+                        "bid": [
+                            {
+                                "id": "test_bid_id",
+                                "impid": "test-imp-id",
+                                "price": 0.27543,
+                                "adm": "<iframe id=\"adm-banner-16\" width=\"300\" height=\"250\" frameborder=\"0\" marginheight=\"0\" marginwidth=\"0\" style=\"{overflow:hidden}\" src=\"https://backend.pubrise.ai/pserver&k=882b2510ed6d6c94fa69c99aa522a708\"></iframe>",
+                                "cid": "test_cid",
+                                "crid": "test_crid",
+                                "dealid": "test_dealid",
+                                "w": 300,
+                                "h": 250,
+                                "ext": {}
+                            }
+                        ],
+                        "seat": "pubrise"
+                    }
+                ],
+                "cur": "USD"
+            }
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "could not define media type for impression: test-imp-id",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/pubrise/pubrisetest/supplemental/bad_response.json b/adapters/pubrise/pubrisetest/supplemental/bad_response.json
new file mode 100644
index 00000000000..08b58d888ed
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/supplemental/bad_response.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://backend.pubrise.ai/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 200,
+			"body": ""
+        }
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/pubrise/pubrisetest/supplemental/no-valid-impressions.json b/adapters/pubrise/pubrisetest/supplemental/no-valid-impressions.json
new file mode 100644
index 00000000000..cc1edd685f9
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/supplemental/no-valid-impressions.json
@@ -0,0 +1,20 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [],
+    "app": {
+      "id": "1",
+      "bundle": "com.wls.testwlsapplication"
+    },
+    "device": {
+      "ip": "123.123.123.123",
+      "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+    }
+  },
+  "expectedMakeRequestsErrors": [
+    {
+      "value": "found no valid impressions",
+      "comparison": "literal"
+    }
+  ]
+}
diff --git a/adapters/pubrise/pubrisetest/supplemental/status-204.json b/adapters/pubrise/pubrisetest/supplemental/status-204.json
new file mode 100644
index 00000000000..1ed98ff0c72
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/supplemental/status-204.json
@@ -0,0 +1,80 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://backend.pubrise.ai/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 204,
+			"body": {}
+		}
+    }],
+    "expectedBidResponses": []
+}
diff --git a/adapters/pubrise/pubrisetest/supplemental/status-not-200.json b/adapters/pubrise/pubrisetest/supplemental/status-not-200.json
new file mode 100644
index 00000000000..c4b3cdc6f57
--- /dev/null
+++ b/adapters/pubrise/pubrisetest/supplemental/status-not-200.json
@@ -0,0 +1,85 @@
+{
+	"mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "test-imp-id",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        },
+                        {
+                            "w": 300,
+                            "h": 600
+                        }
+                    ]
+                },
+                "ext": {
+                    "bidder": {
+                        "placementId": "test"
+                    }
+                }
+            }
+        ],
+        "app": {
+            "id": "1",
+            "bundle": "com.wls.testwlsapplication"
+        },
+        "device": {
+            "ip": "123.123.123.123",
+            "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+        }
+    },
+	"httpCalls": [{
+		"expectedRequest": {
+			"uri": "https://backend.pubrise.ai/pserver",
+			"body": {
+                "id": "test-request-id",
+                "imp": [
+                    {
+                        "id": "test-imp-id",
+                        "banner": {
+                            "format": [
+                                {
+                                    "w": 300,
+                                    "h": 250
+                                },
+                                {
+                                    "w": 300,
+                                    "h": 600
+                                }
+                            ]
+                        },
+                        "ext": {
+                            "bidder": {
+                                "placementId": "test",
+                                "type": "publisher"
+                            }
+                        }
+                    }
+                ],
+                "app": {
+                    "id": "1",
+                    "bundle": "com.wls.testwlsapplication"
+                },
+                "device": {
+                    "ip": "123.123.123.123",
+                    "ifa": "sdjfksdf-dfsds-dsdg-dsgg"
+                }
+            },
+            "impIDs":["test-imp-id"]
+		},
+		"mockResponse": {
+			"status": 404,
+			"body": {}
+		}
+    }],
+    "expectedMakeBidsErrors": [
+        {
+          "value": "Unexpected status code: 404. Run with request.debug = 1 for more info",
+          "comparison": "literal"
+        }
+    ]
+}
diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go
index 2065163c660..47760188438 100644
--- a/adapters/rtbhouse/rtbhouse.go
+++ b/adapters/rtbhouse/rtbhouse.go
@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strconv"
 	"strings"
 
 	"github.com/buger/jsonparser"
@@ -160,8 +161,9 @@ func (adapter *RTBHouseAdapter) MakeBids(
 	var typedBid *adapters.TypedBid
 	for _, seatBid := range openRTBBidderResponse.SeatBid {
 		for _, bid := range seatBid.Bid {
-			bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this
+			bid := bid
 			bidType, err := getMediaTypeForBid(bid)
+			resolveMacros(&bid)
 			if err != nil {
 				errs = append(errs, err)
 				continue
@@ -221,3 +223,11 @@ func getNativeAdm(adm string) (string, error) {
 
 	return adm, nil
 }
+
+func resolveMacros(bid *openrtb2.Bid) {
+	if bid != nil {
+		price := strconv.FormatFloat(bid.Price, 'f', -1, 64)
+		bid.NURL = strings.Replace(bid.NURL, "${AUCTION_PRICE}", price, -1)
+		bid.AdM = strings.Replace(bid.AdM, "${AUCTION_PRICE}", price, -1)
+	}
+}
diff --git a/adapters/rtbhouse/rtbhousetest/exemplary/banner-resolve-macros.json b/adapters/rtbhouse/rtbhousetest/exemplary/banner-resolve-macros.json
new file mode 100644
index 00000000000..eaa3965040e
--- /dev/null
+++ b/adapters/rtbhouse/rtbhousetest/exemplary/banner-resolve-macros.json
@@ -0,0 +1,87 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "site": {
+            "page": "https://good.site/url"
+        },
+        "imp": [{
+            "id": "test-imp-id",
+            "banner": {
+                "format": [{
+                    "w": 300,
+                    "h": 250
+                }]
+            },
+            "ext": {
+                "bidder": {}
+            }
+        }]
+    },
+
+    "httpCalls": [{
+        "expectedRequest": {
+            "uri": "http://localhost/prebid_server",
+            "body": {
+                "id": "test-request-id",
+                "cur": ["USD"],
+                "site": {
+                    "page": "https://good.site/url"
+                },
+                "imp": [{
+                    "id": "test-imp-id",
+                    "banner": {
+                        "format": [{
+                            "w": 300,
+                            "h": 250
+                        }]
+                    },
+                    "ext": {
+                        "bidder": {}
+                    }
+                }]
+            },
+            "impIDs":["test-imp-id"]
+        },
+        "mockResponse": {
+            "status": 200,
+            "body": {
+                "id": "test-request-id",
+                "seatbid": [{
+                    "seat": "rtbhouse",
+                    "bid": [{
+                        "id": "randomid",
+                        "impid": "test-imp-id",
+                        "price": 0.500000,
+                        "adid": "12345678",
+                        "adm": "<iframe src=\"https://endpoint.url/banner1234567890.html\"></iframe><img src=\"https://endpoint.url/win-notify?a=xyz&price=${AUCTION_PRICE}\">",
+                        "cid": "987",
+                        "crid": "12345678",
+                        "h": 250,
+                        "w": 300,
+                        "mtype": 1
+                    }]
+                }],
+                "cur": "USD"
+            }
+        }
+    }],
+
+    "expectedBidResponses": [{
+        "currency": "USD",
+        "bids": [{
+            "bid": {
+                "id": "randomid",
+                "impid": "test-imp-id",
+                "price": 0.5,
+                "adm": "<iframe src=\"https://endpoint.url/banner1234567890.html\"></iframe><img src=\"https://endpoint.url/win-notify?a=xyz&price=0.5\">",
+                "adid": "12345678",
+                "cid": "987",
+                "crid": "12345678",
+                "w": 300,
+                "h": 250,
+                "mtype": 1
+            },
+            "type": "banner"
+        }]
+    }]
+}
diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go
index 01b1e79799e..3ef76f58484 100644
--- a/adapters/rubicon/rubicon.go
+++ b/adapters/rubicon/rubicon.go
@@ -3,6 +3,7 @@ package rubicon
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/prebid/prebid-server/v2/version"
 	"net/http"
 	"net/url"
 	"strconv"
@@ -25,6 +26,7 @@ var bannerExtContent = []byte(`{"rp":{"mime":"text/html"}}`)
 
 type RubiconAdapter struct {
 	URI          string
+	externalURI  string
 	XAPIUsername string
 	XAPIPassword string
 }
@@ -219,6 +221,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 
 	bidder := &RubiconAdapter{
 		URI:          uri,
+		externalURI:  server.ExternalUrl,
 		XAPIUsername: config.XAPI.Username,
 		XAPIPassword: config.XAPI.Password,
 	}
@@ -271,7 +274,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 	rubiconRequest := *request
 	for imp, bidderExt := range impsToExtMap {
 		rubiconExt := bidderExt.Bidder
-		target, err := updateImpRpTargetWithFpdAttributes(bidderExt, rubiconExt, *imp, request.Site, request.App)
+		target, err := a.updateImpRpTarget(bidderExt, rubiconExt, *imp, request.Site, request.App)
 		if err != nil {
 			errs = append(errs, err)
 			continue
@@ -317,9 +320,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada
 			continue
 		}
 
-		if resolvedBidFloor > 0 {
-			imp.BidFloorCur = "USD"
+		if resolvedBidFloor >= 0 {
 			imp.BidFloor = resolvedBidFloor
+			if imp.BidFloorCur != "" {
+				imp.BidFloorCur = "USD"
+			}
 		}
 
 		if request.User != nil {
@@ -647,7 +652,7 @@ func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.Ext
 	return bidFloor, nil
 }
 
-func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon,
+func (a *RubiconAdapter) updateImpRpTarget(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon,
 	imp openrtb2.Imp, site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) {
 
 	existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target")
@@ -740,6 +745,11 @@ func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubico
 	if len(extImpRubicon.Keywords) > 0 {
 		addStringArrayAttribute(extImpRubicon.Keywords, target, "keywords")
 	}
+
+	target["pbs_login"] = a.XAPIUsername
+	target["pbs_version"] = version.Ver
+	target["pbs_url"] = a.externalURI
+
 	updatedTarget, err := json.Marshal(target)
 	if err != nil {
 		return nil, err
diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go
index 7a420a10eca..ba72727c41c 100644
--- a/adapters/rubicon/rubicon_test.go
+++ b/adapters/rubicon/rubicon_test.go
@@ -232,6 +232,14 @@ func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) {
 			expectedBidCur:   "",
 			expectedErrors:   nil,
 		},
+		{
+			bidFloor:         0,
+			bidFloorCur:      "EUR",
+			setMock:          func(m *mock.Mock) {},
+			expectedBidFloor: 0,
+			expectedBidCur:   "USD",
+			expectedErrors:   nil,
+		},
 		{
 			bidFloor:         -1,
 			bidFloorCur:      "CZK",
@@ -600,6 +608,73 @@ func TestOpenRTBFirstPartyDataPopulating(t *testing.T) {
 	}
 }
 
+func TestPbsHostInfoPopulating(t *testing.T) {
+	bidder := RubiconAdapter{
+		URI:          "url",
+		externalURI:  "externalUrl",
+		XAPIUsername: "username",
+		XAPIPassword: "password",
+	}
+
+	request := &openrtb2.BidRequest{
+		ID: "test-request-id",
+		Imp: []openrtb2.Imp{{
+			ID: "test-imp-id",
+			Banner: &openrtb2.Banner{
+				Format: []openrtb2.Format{
+					{W: 300, H: 250},
+				},
+			},
+			Ext: json.RawMessage(`{
+				"bidder": {
+					"zoneId": 8394,
+					"siteId": 283282,
+					"accountId": 7891,
+					"inventory": {"key1" : "val1"},
+					"visitor": {"key2" : "val2"}
+				}
+			}`),
+		}},
+		App: &openrtb2.App{
+			ID:   "com.test",
+			Name: "testApp",
+		},
+	}
+
+	reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{})
+
+	rubiconReq := &openrtb2.BidRequest{}
+	if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil {
+		t.Fatalf("Unexpected error while decoding request: %s", err)
+	}
+
+	var rpImpExt rubiconImpExt
+	if err := json.Unmarshal(rubiconReq.Imp[0].Ext, &rpImpExt); err != nil {
+		t.Fatalf("Error unmarshalling imp.ext: %s", err)
+	}
+
+	var pbsLogin string
+	pbsLogin, err := jsonparser.GetString(rpImpExt.RP.Target, "pbs_login")
+	if err != nil {
+		t.Fatal("Error extracting pbs_login")
+	}
+	assert.Equal(t, pbsLogin, "username", "Unexpected pbs_login value")
+
+	var pbsVersion string
+	pbsVersion, err = jsonparser.GetString(rpImpExt.RP.Target, "pbs_version")
+	if err != nil {
+		t.Fatal("Error extracting pbs_version")
+	}
+	assert.Equal(t, pbsVersion, "", "Unexpected pbs_version value")
+
+	var pbsUrl string
+	pbsUrl, err = jsonparser.GetString(rpImpExt.RP.Target, "pbs_url")
+	if err != nil {
+		t.Fatal("Error extracting pbs_url")
+	}
+	assert.Equal(t, pbsUrl, "externalUrl", "Unexpected pbs_url value")
+}
+
 func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) {
 	bidder := new(RubiconAdapter)
 
@@ -982,7 +1057,7 @@ func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) {
 				"siteId": 68780,
 				"zoneId": 327642,
 				"debug": {
-					"cpmoverride" : 20 
+					"cpmoverride" : 20
 				}
 			}}`),
 		}},
diff --git a/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
index 581a0eb5308..fbbe72332dd 100644
--- a/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
+++ b/adapters/rubicon/rubicontest/exemplary/25-26-transition-period.json
@@ -364,7 +364,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json
index 279daec96c9..8bde0ff8004 100644
--- a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json
+++ b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json
@@ -349,7 +349,10 @@
                     "sectioncat": [
                       "sectionCat1",
                       "sectionCat2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json
index 71043771d1d..0f3c97dc1e4 100644
--- a/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json
+++ b/adapters/rubicon/rubicontest/exemplary/bidonmultiformat.json
@@ -106,7 +106,10 @@
                     ],
                     "search": [
                       "someSearch"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
@@ -212,7 +215,10 @@
                     ],
                     "search": [
                       "someSearch"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/flexible-schema.json b/adapters/rubicon/rubicontest/exemplary/flexible-schema.json
index 5af9224a1ea..f2fa5b58cbc 100644
--- a/adapters/rubicon/rubicontest/exemplary/flexible-schema.json
+++ b/adapters/rubicon/rubicontest/exemplary/flexible-schema.json
@@ -349,7 +349,10 @@
                     "sectioncat": [
                       "sectionCat1",
                       "sectionCat2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/hardcode-secure.json b/adapters/rubicon/rubicontest/exemplary/hardcode-secure.json
index dc3b9f64d2b..c1842cbe8fb 100644
--- a/adapters/rubicon/rubicontest/exemplary/hardcode-secure.json
+++ b/adapters/rubicon/rubicontest/exemplary/hardcode-secure.json
@@ -323,7 +323,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat.json b/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat.json
index 73fc01a51e2..3e583e6741c 100644
--- a/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat.json
+++ b/adapters/rubicon/rubicontest/exemplary/non-bidonmultiformat.json
@@ -105,7 +105,10 @@
                     ],
                     "search": [
                       "someSearch"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-banner.json b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
index 83073f0cb71..bfe9bb24926 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-banner.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-banner.json
@@ -323,7 +323,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-native.json b/adapters/rubicon/rubicontest/exemplary/simple-native.json
index 976d05c1d5d..a3bdeb49bff 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-native.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-native.json
@@ -307,7 +307,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json
index 3546af36dbd..b022f516cf5 100644
--- a/adapters/rubicon/rubicontest/exemplary/simple-video.json
+++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json
@@ -322,7 +322,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json
index ff9b6384905..a5b7fe9dd73 100644
--- a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json
+++ b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json
@@ -473,7 +473,10 @@
                       "sectionCat1",
                       "sectionCat2"
                     ],
-                    "dfp_ad_unit_code": "adSlotFromData"
+                    "dfp_ad_unit_code": "adSlotFromData",
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/exemplary/user-fpd.json b/adapters/rubicon/rubicontest/exemplary/user-fpd.json
index 0c4e1a95592..97aaa1bf68a 100644
--- a/adapters/rubicon/rubicontest/exemplary/user-fpd.json
+++ b/adapters/rubicon/rubicontest/exemplary/user-fpd.json
@@ -275,7 +275,10 @@
                     "search": [
                       "someSearch"
                     ],
-                    "dfp_ad_unit_code": "someAdSlot"
+                    "dfp_ad_unit_code": "someAdSlot",
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json
index e10806cdc14..6556568ca36 100644
--- a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json
+++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json
@@ -234,7 +234,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json
index a86127b5822..aac2800c210 100644
--- a/adapters/rubicon/rubicontest/supplemental/no-site-content.json
+++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json
@@ -230,7 +230,10 @@
                     "pagecat": [
                       "val1",
                       "val2"
-                    ]
+                    ],
+                    "pbs_login": "xuser",
+                    "pbs_url": "http://hosturl.com",
+                    "pbs_version": ""
                   },
                   "track": {
                     "mint": "",
diff --git a/adapters/smaato/smaatotest/supplemental/no-app-site-request.json b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json
deleted file mode 100644
index 04a73b4f40d..00000000000
--- a/adapters/smaato/smaatotest/supplemental/no-app-site-request.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "mockBidRequest": {
-    "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
-    "imp": [
-      {
-        "id": "1C86242D-9535-47D6-9576-7B1FE87F282C",
-        "banner": {
-          "format": [
-            {
-              "w": 320,
-              "h": 50
-            }
-          ]
-        },
-        "ext": {
-          "bidder": {
-            "publisherId": "1100042525",
-            "adspaceId": "130563103"
-          }
-        }
-      }
-    ]
-  },
-  "expectedMakeRequestsErrors": [
-    {
-      "value": "Missing Site/App.",
-      "comparison": "literal"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go
index 385149f1517..07e301cd7a7 100644
--- a/adapters/sonobi/sonobi.go
+++ b/adapters/sonobi/sonobi.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v2/adapters"
@@ -53,6 +54,25 @@ func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adap
 
 		reqCopy.Imp[0].TagID = sonobiExt.TagID
 
+		// If the bid floor currency is not USD, do the conversion to USD
+		if reqCopy.Imp[0].BidFloor > 0 && reqCopy.Imp[0].BidFloorCur != "" && strings.ToUpper(reqCopy.Imp[0].BidFloorCur) != "USD" {
+
+			// Convert to US dollars
+			convertedValue, err := reqInfo.ConvertCurrency(reqCopy.Imp[0].BidFloor, reqCopy.Imp[0].BidFloorCur, "USD")
+			if err != nil {
+				errs = append(errs, err)
+				continue
+			}
+
+			// Update after conversion. All imp elements inside request.Imp are shallow copies
+			// therefore, their non-pointer values are not shared memory and are safe to modify.
+			reqCopy.Imp[0].BidFloorCur = "USD"
+			reqCopy.Imp[0].BidFloor = convertedValue
+		}
+
+		// Sonobi only bids in USD
+		reqCopy.Cur = append(make([]string, 0, 1), "USD")
+
 		adapterReq, errors := a.makeRequest(&reqCopy)
 		if adapterReq != nil {
 			adapterRequests = append(adapterRequests, adapterReq)
@@ -114,19 +134,19 @@ func (a *SonobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalR
 	}
 
 	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+	bidResponse.Currency = "USD" // Sonobi only bids in USD
 
 	for _, sb := range bidResp.SeatBid {
 		for i := range sb.Bid {
-			bidType, err := getMediaTypeForImp(sb.Bid[i].ImpID, internalRequest.Imp)
+			bid := sb.Bid[i]
+			bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp)
 			if err != nil {
-				errs = append(errs, err)
-			} else {
-				b := &adapters.TypedBid{
-					Bid:     &sb.Bid[i],
-					BidType: bidType,
-				}
-				bidResponse.Bids = append(bidResponse.Bids, b)
+				return nil, []error{err}
 			}
+			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+				Bid:     &bid,
+				BidType: bidType,
+			})
 		}
 	}
 	return bidResponse, errs
@@ -139,6 +159,9 @@ func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType,
 			if imp.Banner == nil && imp.Video != nil {
 				mediaType = openrtb_ext.BidTypeVideo
 			}
+			if imp.Banner == nil && imp.Video == nil && imp.Native != nil {
+				mediaType = openrtb_ext.BidTypeNative
+			}
 			return mediaType, nil
 		}
 	}
diff --git a/adapters/sonobi/sonobitest/exemplary/banner.json b/adapters/sonobi/sonobitest/exemplary/banner.json
index 06a7a6724a7..8e7ddd49545 100644
--- a/adapters/sonobi/sonobitest/exemplary/banner.json
+++ b/adapters/sonobi/sonobitest/exemplary/banner.json
@@ -1,5 +1,6 @@
 {
     "mockBidRequest": {
+        "cur": ["GBP"],
         "id": "some-request-id",
         "site": {
             "page": "http://tester.go.sonobi.com",
@@ -48,6 +49,7 @@
                 },
                 "uri": "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af",
                 "body": {
+                    "cur": ["USD"],
                     "id": "some-request-id",
                     "imp": [
                         {
diff --git a/adapters/sonobi/sonobitest/exemplary/native.json b/adapters/sonobi/sonobitest/exemplary/native.json
new file mode 100644
index 00000000000..ff9eb4c4693
--- /dev/null
+++ b/adapters/sonobi/sonobitest/exemplary/native.json
@@ -0,0 +1,143 @@
+{
+  "mockBidRequest": {
+    "cur": ["USD"],
+    "id": "some-request-id",
+    "site": {
+      "page": "http://tester.go.sonobi.com",
+      "domain": "sonobi.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "native": {
+          "request": "{\"ver\":\"1.2\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":2,\"plcmtcnt\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":1000}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":600,\"hmin\":600}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":3000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":60}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}},{\"id\":10,\"required\":0,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"privacy\":1}",
+          "ver": "1.2",
+          "battr": [
+            1,
+            2,
+            6,
+            7,
+            8,
+            9,
+            10,
+            14
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "TagID": "/7780971/apex_3pdm_integration"
+          }
+        }
+      }
+    ],
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af",
+        "body": {
+          "cur": ["USD"],
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "native": {
+                "request": "{\"ver\":\"1.2\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":2,\"plcmtcnt\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":1000}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":600,\"hmin\":600}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":3000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":60}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}},{\"id\":10,\"required\":0,\"data\":{\"type\":12,\"len\":15}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]},{\"event\":2,\"methods\":[1]}],\"privacy\":1}",
+                "ver": "1.2",
+                "battr": [
+                  1,
+                  2,
+                  6,
+                  7,
+                  8,
+                  9,
+                  10,
+                  14
+                ]
+              },
+              "tagid": "/7780971/apex_3pdm_integration",
+              "ext": {
+                "bidder": {
+                  "TagID": "/7780971/apex_3pdm_integration"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "sonobi.com",
+            "page": "http://tester.go.sonobi.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        },
+        "impIDs": [
+          "some-impression-id"
+        ]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "price": 2.8649999999999998,
+                  "adm": "test-markup",
+                  "adomain": [
+                    "sonobi.com"
+                  ],
+                  "cid": "house",
+                  "crid": "sandbox"
+
+                }
+              ],
+              "seat": "sonobi"
+            }
+          ],
+          "bidid": "sandbox_642305097",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "price": 2.8649999999999998,
+            "adm": "test-markup",
+            "adomain": [
+              "sonobi.com"
+            ],
+            "cid": "house",
+            "crid": "sandbox"
+          },
+          "type": "native"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sonobi/sonobitest/exemplary/no-bid.json b/adapters/sonobi/sonobitest/exemplary/no-bid.json
index e931dbdcd9e..5f3c2afb1dd 100644
--- a/adapters/sonobi/sonobitest/exemplary/no-bid.json
+++ b/adapters/sonobi/sonobitest/exemplary/no-bid.json
@@ -1,5 +1,6 @@
 {
   "mockBidRequest": {
+    "cur": ["USD"],
     "id": "some-request-id",
     "site": {
       "page": "http://tester.go.sonobi.com",
@@ -48,6 +49,7 @@
         },
         "uri": "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af",
         "body": {
+          "cur": ["USD"],
           "id": "some-request-id",
           "imp": [
             {
diff --git a/adapters/sonobi/sonobitest/supplemental/currency-conversion.json b/adapters/sonobi/sonobitest/supplemental/currency-conversion.json
new file mode 100644
index 00000000000..522e1bd7326
--- /dev/null
+++ b/adapters/sonobi/sonobitest/supplemental/currency-conversion.json
@@ -0,0 +1,172 @@
+{
+  "mockBidRequest": {
+    "cur": ["GBP"],
+    "id": "some-request-id",
+    "site": {
+      "page": "http://tester.go.sonobi.com",
+      "domain": "sonobi.com"
+    },
+    "device": {
+      "ip": "123.123.123.123"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "w": 300,
+          "h": 250,
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "bidfloor": 1.00,
+        "bidfloorcur": "GBP",
+        "ext": {
+          "bidder": {
+            "TagID": "/7780971/apex_3pdm_integration"
+          }
+        }
+      }
+    ],
+    "ext": {
+      "prebid": {
+        "currency": {
+          "rates": {
+            "GBP": {
+              "USD": 0.05
+            }
+          },
+          "usepbsrates": false
+        }
+      }
+    },
+    "test": 1,
+    "tmax": 500
+  },
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Accept": [
+            "application/json"
+          ],
+          "Content-Type": [
+            "application/json;charset=utf-8"
+          ]
+        },
+        "uri": "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af",
+        "body": {
+          "cur": ["USD"],
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "some-impression-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ],
+                "w": 300,
+                "h": 250
+              },
+              "bidfloor": 0.05,
+              "bidfloorcur": "USD",
+              "tagid": "/7780971/apex_3pdm_integration",
+              "ext": {
+                "bidder": {
+                  "TagID": "/7780971/apex_3pdm_integration"
+                }
+              }
+            }
+          ],
+          "ext": {
+            "prebid": {
+              "currency": {
+                "rates": {
+                  "GBP": {
+                    "USD": 0.05
+                  }
+                },
+                "usepbsrates": false
+              }
+            }
+          },
+          "site": {
+            "domain": "sonobi.com",
+            "page": "http://tester.go.sonobi.com"
+          },
+          "device": {
+            "ip": "123.123.123.123"
+          },
+          "test": 1,
+          "tmax": 500
+        },
+        "impIDs":["some-impression-id"]
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "some-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "1",
+                  "impid": "some-impression-id",
+                  "price": 2.8649999999999998,
+                  "adm": "<script type=\"text/javascript\" src=\"http://iad-2.go.sonobi.com/sbid.js?demand&aid=sandbox_642305097&aic=${AUCTION_PRICE}&pid=71d9d3d8af&h=6.137\"></script>",
+                  "adomain": [
+                    "sonobi.com"
+                  ],
+                  "cid": "house",
+                  "crid": "sandbox",
+                  "h": 1,
+                  "w": 1
+                }
+              ],
+              "seat": "sonobi"
+            }
+          ],
+          "bidid": "sandbox_642305097",
+          "cur": "USD"
+        }
+      }
+    }
+  ],
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "1",
+            "impid": "some-impression-id",
+            "price": 2.8649999999999998,
+            "adm": "<script type=\"text/javascript\" src=\"http://iad-2.go.sonobi.com/sbid.js?demand&aid=sandbox_642305097&aic=${AUCTION_PRICE}&pid=71d9d3d8af&h=6.137\"></script>",
+            "adomain": [
+              "sonobi.com"
+            ],
+            "cid": "house",
+            "crid": "sandbox",
+            "h": 1,
+            "w": 1
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/taboola/taboola.go b/adapters/taboola/taboola.go
index 85a129eb341..01caafed610 100644
--- a/adapters/taboola/taboola.go
+++ b/adapters/taboola/taboola.go
@@ -131,7 +131,14 @@ func (a *adapter) buildRequest(request *openrtb2.BidRequest) (*adapters.RequestD
 		return nil, fmt.Errorf("unsupported media type for imp: %v", request.Imp[0])
 	}
 
-	url, err := a.buildEndpointURL(request.Site.ID, mediaType)
+	var taboolaPublisherId string
+	if request.Site != nil && request.Site.ID != "" {
+		taboolaPublisherId = request.Site.ID
+	} else if request.App != nil && request.App.ID != "" {
+		taboolaPublisherId = request.App.ID
+	}
+
+	url, err := a.buildEndpointURL(taboolaPublisherId, mediaType)
 	if err != nil {
 		return nil, err
 	}
@@ -206,22 +213,20 @@ func createTaboolaRequests(request *openrtb2.BidRequest) (taboolaRequests []*ope
 		ID: taboolaExt.PublisherId,
 	}
 
-	if modifiedRequest.Site == nil {
-		newSite := &openrtb2.Site{
-			ID:        taboolaExt.PublisherId,
-			Name:      taboolaExt.PublisherId,
-			Domain:    evaluateDomain(taboolaExt.PublisherDomain, request),
-			Publisher: publisher,
-		}
-		modifiedRequest.Site = newSite
-	} else {
+	if modifiedRequest.Site != nil {
 		modifiedSite := *modifiedRequest.Site
-		modifiedSite.Publisher = publisher
 		modifiedSite.ID = taboolaExt.PublisherId
 		modifiedSite.Name = taboolaExt.PublisherId
 		modifiedSite.Domain = evaluateDomain(taboolaExt.PublisherDomain, request)
+		modifiedSite.Publisher = publisher
 		modifiedRequest.Site = &modifiedSite
 	}
+	if modifiedRequest.App != nil {
+		modifiedApp := *modifiedRequest.App
+		modifiedApp.ID = taboolaExt.PublisherId
+		modifiedApp.Publisher = publisher
+		modifiedRequest.App = &modifiedApp
+	}
 
 	if taboolaExt.BCat != nil {
 		modifiedRequest.BCat = taboolaExt.BCat
diff --git a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json b/adapters/taboola/taboolatest/exemplary/bannerAppRequest.json
similarity index 92%
rename from adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
rename to adapters/taboola/taboolatest/exemplary/bannerAppRequest.json
index 6e01c457aea..60cec3ca1d5 100644
--- a/adapters/taboola/taboolatest/supplemental/emptySiteInRequest.json
+++ b/adapters/taboola/taboolatest/exemplary/bannerAppRequest.json
@@ -23,13 +23,14 @@
         "ext": {
           "bidder": {
             "publisherId": "publisher-id",
-            "tagid": "tag-id"
+            "tagid": "tag-id",
+            "tagId": "tag-Id"
           }
         }
       }
     ],
     "app": {
-      "domain": "http://domain.com"
+      "bundle": "com.app.my"
     },
     "device": {
       "ua": "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.62 Mobile Safari/537.36",
@@ -62,21 +63,19 @@
                   }
                 ]
               },
-              "tagid" : "tag-id",
+              "tagid" : "tag-Id",
               "ext": {
                 "bidder": {
                   "publisherId": "publisher-id",
-                  "tagid": "tag-id"
+                  "tagid": "tag-id",
+                  "tagId": "tag-Id"
                 }
               }
             }
           ],
           "app": {
-            "domain": "http://domain.com"
-          },
-          "site": {
             "id": "publisher-id",
-            "name": "publisher-id",
+            "bundle": "com.app.my",
             "publisher": {
               "id": "publisher-id"
             }
diff --git a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json
index b040d31b5f6..5538150f450 100644
--- a/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json
+++ b/adapters/yeahmobi/yeahmobitest/exemplary/simple-video.json
@@ -20,7 +20,6 @@
       }
     ]
   },
-
   "httpCalls": [
     {
       "expectedRequest": {
@@ -29,7 +28,7 @@
           "id": "test-request-id",
           "imp": [
             {
-              "id":"test-imp-id",
+              "id": "test-imp-id",
               "video": {
                 "w": 300,
                 "h": 250,
@@ -45,7 +44,10 @@
               }
             }
           ]
-        }
+        },
+        "impIDs": [
+          "test-imp-id"
+        ]
       },
       "mockResponse": {
         "status": 200,
@@ -54,13 +56,20 @@
           "seatbid": [
             {
               "seat": "ttx",
-              "bid": [{
-                "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
-                "impid": "test-imp-id",
-                "price": 1.2,
-                "adm": "some-ads",
-                "crid": "crid_testid"
-              }]
+              "bid": [
+                {
+                  "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
+                  "impid": "test-imp-id",
+                  "price": 1.2,
+                  "adm": "some-ads",
+                  "crid": "crid_testid",
+                  "ext": {
+                    "video": {
+                      "duration": 300
+                    }
+                  }
+                }
+              ]
             }
           ],
           "cur": "USD"
@@ -68,7 +77,6 @@
       }
     }
   ],
-
   "expectedBidResponses": [
     {
       "currency": "USD",
@@ -79,9 +87,18 @@
             "impid": "test-imp-id",
             "price": 1.2,
             "adm": "some-ads",
-            "crid": "crid_testid"
+            "crid": "crid_testid",
+            "ext": {
+              "video": {
+                "duration": 300
+              }
+            }
           },
-          "type": "video"
+          "type": "video",
+          "video": {
+            "duration": 300,
+            "primary_category": ""
+          }
         }
       ]
     }
diff --git a/amp/parse.go b/amp/parse.go
index 12663ee93bd..a39888eee9f 100644
--- a/amp/parse.go
+++ b/amp/parse.go
@@ -117,7 +117,7 @@ func buildGdprTCF2ConsentWriter(ampParams Params) gdpr.ConsentWriter {
 		// set regs.ext.gdpr if non-nil gdpr_applies was set to true
 		gdprValue = parseGdprApplies(ampParams.GdprApplies)
 	}
-	writer.RegExtGDPR = &gdprValue
+	writer.GDPR = &gdprValue
 
 	return writer
 }
diff --git a/amp/parse_test.go b/amp/parse_test.go
index f2f097284c5..98006325e72 100644
--- a/amp/parse_test.go
+++ b/amp/parse_test.go
@@ -311,8 +311,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-							RegExtGDPR: &int8One,
+							Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+							GDPR:    &int8One,
 						},
 						warning: nil,
 					},
@@ -378,8 +378,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-							RegExtGDPR: &int8One,
+							Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+							GDPR:    &int8One,
 						},
 						warning: nil,
 					},
@@ -400,8 +400,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "INVALID_GDPR",
-							RegExtGDPR: &int8One,
+							Consent: "INVALID_GDPR",
+							GDPR:    &int8One,
 						},
 						warning: &errortypes.Warning{
 							Message:     "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.",
@@ -420,8 +420,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "INVALID_GDPR",
-							RegExtGDPR: &int8Zero,
+							Consent: "INVALID_GDPR",
+							GDPR:    &int8Zero,
 						},
 						warning: &errortypes.Warning{
 							Message:     "Consent string 'INVALID_GDPR' is not a valid TCF2 consent string.",
@@ -440,8 +440,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-							RegExtGDPR: &int8Zero,
+							Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+							GDPR:    &int8Zero,
 						},
 						warning: nil,
 					},
@@ -457,8 +457,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-							RegExtGDPR: &int8One,
+							Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+							GDPR:    &int8One,
 						},
 						warning: nil,
 					},
@@ -473,8 +473,8 @@ func TestPrivacyReader(t *testing.T) {
 					},
 					expected: expectedResults{
 						policyWriter: gdpr.ConsentWriter{
-							Consent:    "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-							RegExtGDPR: &int8One,
+							Consent: "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
+							GDPR:    &int8One,
 						},
 						warning: nil,
 					},
@@ -559,8 +559,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) {
 			desc:     "gdpr_applies not set",
 			inParams: Params{Consent: consentString},
 			expectedWriter: gdpr.ConsentWriter{
-				Consent:    consentString,
-				RegExtGDPR: &int8One,
+				Consent: consentString,
+				GDPR:    &int8One,
 			},
 		},
 		{
@@ -570,8 +570,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) {
 				GdprApplies: &boolFalse,
 			},
 			expectedWriter: gdpr.ConsentWriter{
-				Consent:    consentString,
-				RegExtGDPR: &int8Zero,
+				Consent: consentString,
+				GDPR:    &int8Zero,
 			},
 		},
 		{
@@ -581,8 +581,8 @@ func TestBuildGdprTCF2ConsentWriter(t *testing.T) {
 				GdprApplies: &boolTrue,
 			},
 			expectedWriter: gdpr.ConsentWriter{
-				Consent:    consentString,
-				RegExtGDPR: &int8One,
+				Consent: consentString,
+				GDPR:    &int8One,
 			},
 		},
 	}
diff --git a/analytics/agma/README.md b/analytics/agma/README.md
new file mode 100644
index 00000000000..430001863ac
--- /dev/null
+++ b/analytics/agma/README.md
@@ -0,0 +1,28 @@
+# agma Analytics
+
+In order to use the Agma Analytics Adapter, please adjust the accounts / endpoint with the data provided by agma (https://www.agma-mmc.de).
+
+## Configuration
+
+```yaml
+analytics:
+    agma:
+        # Required: enable the module
+        enabled: true
+        # Required: set the accounts you want to track
+        accounts:
+        - code: "my-code" # Required: provied by agma
+          publisher_id: "123" # Required: Exchange specific publisher_id, can be an empty string accounts are not used
+          site_app_id: "openrtb2-site.id-or-app.id-or-app.bundle" # optional: scope to the publisher with an openrtb2 Site object id or App object id/bundle
+        # Optional properties (advanced configuration)
+        endpoint: 
+            url: "https://go.pbs.agma-analytics.de/v1/prebid-server" # Check with agma if your site needs an extra url
+            timeout: "2s"
+            gzip: true
+        buffers: # Flush events when (first condition reached)
+            # Size of the buffer in bytes
+            size: "2MB" # greater than 2MB (size using SI standard eg. "44kB", "17MB")
+            count : 100 # greater than 100 events
+            timeout: "15m" # greater than 15 minutes (parsed as golang duration)
+
+```
diff --git a/analytics/agma/agma_module.go b/analytics/agma/agma_module.go
new file mode 100644
index 00000000000..58133db2608
--- /dev/null
+++ b/analytics/agma/agma_module.go
@@ -0,0 +1,271 @@
+package agma
+
+import (
+	"bytes"
+	"errors"
+	"net/http"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+	"time"
+
+	"github.com/benbjohnson/clock"
+	"github.com/docker/go-units"
+	"github.com/golang/glog"
+	"github.com/prebid/go-gdpr/vendorconsent"
+	"github.com/prebid/prebid-server/v2/analytics"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+)
+
+type httpSender = func(payload []byte) error
+
+const (
+	agmaGVLID = 1122
+	p9        = 9
+)
+
+type AgmaLogger struct {
+	sender            httpSender
+	clock             clock.Clock
+	accounts          []config.AgmaAnalyticsAccount
+	eventCount        int64
+	maxEventCount     int64
+	maxBufferByteSize int64
+	maxDuration       time.Duration
+	mux               sync.RWMutex
+	sigTermCh         chan os.Signal
+	buffer            bytes.Buffer
+	bufferCh          chan []byte
+}
+
+func newAgmaLogger(cfg config.AgmaAnalytics, sender httpSender, clock clock.Clock) (*AgmaLogger, error) {
+	pSize, err := units.FromHumanSize(cfg.Buffers.BufferSize)
+	if err != nil {
+		return nil, err
+	}
+	pDuration, err := time.ParseDuration(cfg.Buffers.Timeout)
+	if err != nil {
+		return nil, err
+	}
+	if len(cfg.Accounts) == 0 {
+		return nil, errors.New("Please configure at least one account for Agma Analytics")
+	}
+
+	buffer := bytes.Buffer{}
+	buffer.Write([]byte("["))
+
+	return &AgmaLogger{
+		sender:            sender,
+		clock:             clock,
+		accounts:          cfg.Accounts,
+		maxBufferByteSize: pSize,
+		eventCount:        0,
+		maxEventCount:     int64(cfg.Buffers.EventCount),
+		maxDuration:       pDuration,
+		buffer:            buffer,
+		bufferCh:          make(chan []byte),
+		sigTermCh:         make(chan os.Signal, 1),
+	}, nil
+}
+
+func NewModule(httpClient *http.Client, cfg config.AgmaAnalytics, clock clock.Clock) (analytics.Module, error) {
+	sender, err := createHttpSender(httpClient, cfg.Endpoint)
+	if err != nil {
+		return nil, err
+	}
+
+	m, err := newAgmaLogger(cfg, sender, clock)
+	if err != nil {
+		return nil, err
+	}
+
+	signal.Notify(m.sigTermCh, os.Interrupt, syscall.SIGTERM)
+
+	go m.start()
+
+	return m, nil
+}
+
+func (l *AgmaLogger) start() {
+	ticker := l.clock.Ticker(l.maxDuration)
+	for {
+		select {
+		case <-l.sigTermCh:
+			glog.Infof("[AgmaAnalytics] Received Close, trying to flush buffer")
+			l.flush()
+			return
+		case event := <-l.bufferCh:
+			l.bufferEvent(event)
+			if l.isFull() {
+				l.flush()
+			}
+		case <-ticker.C:
+			l.flush()
+		}
+	}
+}
+
+func (l *AgmaLogger) bufferEvent(data []byte) {
+	l.mux.Lock()
+	defer l.mux.Unlock()
+
+	l.buffer.Write(data)
+	l.buffer.WriteByte(',')
+	l.eventCount++
+}
+
+func (l *AgmaLogger) isFull() bool {
+	l.mux.RLock()
+	defer l.mux.RUnlock()
+	return l.eventCount >= l.maxEventCount || int64(l.buffer.Len()) >= l.maxBufferByteSize
+}
+
+func (l *AgmaLogger) flush() {
+	l.mux.Lock()
+
+	if l.eventCount == 0 || l.buffer.Len() == 0 {
+		l.mux.Unlock()
+		return
+	}
+
+	// Close the json array, remove last ,
+	l.buffer.Truncate(l.buffer.Len() - 1)
+	l.buffer.Write([]byte("]"))
+
+	payload := make([]byte, l.buffer.Len())
+	_, err := l.buffer.Read(payload)
+	if err != nil {
+		l.reset()
+		l.mux.Unlock()
+		glog.Warning("[AgmaAnalytics] fail to copy the buffer")
+		return
+	}
+
+	go l.sender(payload)
+
+	l.reset()
+	l.mux.Unlock()
+}
+
+func (l *AgmaLogger) reset() {
+	l.buffer.Reset()
+	l.buffer.Write([]byte("["))
+	l.eventCount = 0
+}
+
+func (l *AgmaLogger) extractPublisherAndSite(requestWrapper *openrtb_ext.RequestWrapper) (string, string) {
+	publisherId := ""
+	appSiteId := ""
+	if requestWrapper.Site != nil {
+		if requestWrapper.Site.Publisher != nil {
+			publisherId = requestWrapper.Site.Publisher.ID
+		}
+		appSiteId = requestWrapper.Site.ID
+	}
+	if requestWrapper.App != nil {
+		if requestWrapper.App.Publisher != nil {
+			publisherId = requestWrapper.App.Publisher.ID
+		}
+		appSiteId = requestWrapper.App.ID
+		if appSiteId == "" {
+			appSiteId = requestWrapper.App.Bundle
+		}
+
+	}
+	return publisherId, appSiteId
+}
+
+func (l *AgmaLogger) shouldTrackEvent(requestWrapper *openrtb_ext.RequestWrapper) (bool, string) {
+	if requestWrapper.User == nil {
+		return false, ""
+	}
+	consentStr := requestWrapper.User.Consent
+
+	parsedConsent, err := vendorconsent.ParseString(consentStr)
+	if err != nil {
+		return false, ""
+	}
+
+	p9Allowed := parsedConsent.PurposeAllowed(p9)
+	agmaAllowed := parsedConsent.VendorConsent(agmaGVLID)
+	if !p9Allowed || !agmaAllowed {
+		return false, ""
+	}
+
+	publisherId, appSiteId := l.extractPublisherAndSite(requestWrapper)
+	if publisherId == "" && appSiteId == "" {
+		return false, ""
+	}
+
+	for _, account := range l.accounts {
+		if account.PublisherId == publisherId {
+			if account.SiteAppId == "" {
+				return true, account.Code
+			}
+			if account.SiteAppId == appSiteId {
+				return true, account.Code
+			}
+		}
+	}
+
+	return false, ""
+}
+
+func (l *AgmaLogger) LogAuctionObject(event *analytics.AuctionObject) {
+	if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil {
+		return
+	}
+	shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper)
+	if !shouldTrack {
+		return
+	}
+	data, err := serializeAnayltics(event.RequestWrapper, EventTypeAuction, code, event.StartTime)
+	if err != nil {
+		glog.Errorf("[AgmaAnalytics] Error serializing auction object: %v", err)
+		return
+	}
+	l.bufferCh <- data
+}
+
+func (l *AgmaLogger) LogAmpObject(event *analytics.AmpObject) {
+	if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil {
+		return
+	}
+	shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper)
+	if !shouldTrack {
+		return
+	}
+	data, err := serializeAnayltics(event.RequestWrapper, EventTypeAmp, code, event.StartTime)
+	if err != nil {
+		glog.Errorf("[AgmaAnalytics] Error serializing amp object: %v", err)
+		return
+	}
+	l.bufferCh <- data
+}
+
+func (l *AgmaLogger) LogVideoObject(event *analytics.VideoObject) {
+	if event == nil || event.Status != http.StatusOK || event.RequestWrapper == nil {
+		return
+	}
+	shouldTrack, code := l.shouldTrackEvent(event.RequestWrapper)
+	if !shouldTrack {
+		return
+	}
+	data, err := serializeAnayltics(event.RequestWrapper, EventTypeVideo, code, event.StartTime)
+	if err != nil {
+		glog.Errorf("[AgmaAnalytics] Error serializing video object: %v", err)
+		return
+	}
+	l.bufferCh <- data
+}
+
+func (l *AgmaLogger) Shutdown() {
+	glog.Info("[AgmaAnalytics] Shutdown, trying to flush buffer")
+	l.flush() // mutex safe
+}
+
+func (l *AgmaLogger) LogCookieSyncObject(event *analytics.CookieSyncObject)         {}
+func (l *AgmaLogger) LogNotificationEventObject(event *analytics.NotificationEvent) {}
+func (l *AgmaLogger) LogSetUIDObject(event *analytics.SetUIDObject)                 {}
diff --git a/analytics/agma/agma_module_test.go b/analytics/agma/agma_module_test.go
new file mode 100644
index 00000000000..213af1860be
--- /dev/null
+++ b/analytics/agma/agma_module_test.go
@@ -0,0 +1,735 @@
+package agma
+
+import (
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"sync"
+	"syscall"
+	"testing"
+	"time"
+
+	"github.com/benbjohnson/clock"
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/analytics"
+	"github.com/prebid/prebid-server/v2/config"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+var agmaConsent = "CP6-v9RP6-v9RNlAAAENCZCAAICAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A"
+
+var mockValidAuctionObject = analytics.AuctionObject{
+	Status:    http.StatusOK,
+	StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
+	RequestWrapper: &openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			Site: &openrtb2.Site{
+				ID: "track-me-site",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+			Device: &openrtb2.Device{
+				UA: "ua",
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	},
+}
+
+var mockValidVideoObject = analytics.VideoObject{
+	Status:    http.StatusOK,
+	StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
+	RequestWrapper: &openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			App: &openrtb2.App{
+				ID: "track-me-app",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+			Device: &openrtb2.Device{
+				UA: "ua",
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	},
+}
+
+var mockValidAmpObject = analytics.AmpObject{
+	Status:    http.StatusOK,
+	StartTime: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC),
+	RequestWrapper: &openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			Site: &openrtb2.Site{
+				ID: "track-me-site",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+			Device: &openrtb2.Device{
+				UA: "ua",
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	},
+}
+
+var mockValidAccounts = []config.AgmaAnalyticsAccount{
+	{
+		PublisherId: "track-me",
+		Code:        "abc",
+		SiteAppId:   "track-me-app",
+	},
+	{
+		PublisherId: "track-me",
+		Code:        "abcd",
+		SiteAppId:   "track-me-site",
+	},
+}
+
+type MockedSender struct {
+	mock.Mock
+}
+
+func (m *MockedSender) Send(payload []byte) error {
+	args := m.Called(payload)
+	return args.Error(0)
+}
+
+func TestConfigParsingError(t *testing.T) {
+	testCases := []struct {
+		name       string
+		config     config.AgmaAnalytics
+		shouldFail bool
+	}{
+		{
+			name: "Test with invalid/empty URL",
+			config: config.AgmaAnalytics{
+				Enabled: true,
+				Endpoint: config.AgmaAnalyticsHttpEndpoint{
+					Url:     "%%2815197306101420000%29",
+					Timeout: "1s",
+					Gzip:    false,
+				},
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Test with invalid timout",
+			config: config.AgmaAnalytics{
+				Enabled: true,
+				Endpoint: config.AgmaAnalyticsHttpEndpoint{
+					Url:     "http://localhost:8000/event",
+					Timeout: "1x",
+					Gzip:    false,
+				},
+			},
+			shouldFail: true,
+		},
+		{
+			name: "Test with no accounts",
+			config: config.AgmaAnalytics{
+				Enabled: true,
+				Endpoint: config.AgmaAnalyticsHttpEndpoint{
+					Url:     "http://localhost:8000/event",
+					Timeout: "1s",
+					Gzip:    false,
+				},
+				Buffers: config.AgmaAnalyticsBuffer{
+					EventCount: 1,
+					BufferSize: "1Kb",
+					Timeout:    "1s",
+				},
+				Accounts: []config.AgmaAnalyticsAccount{},
+			},
+			shouldFail: true,
+		},
+	}
+	clockMock := clock.NewMock()
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			_, err := NewModule(&http.Client{}, tc.config, clockMock)
+			if tc.shouldFail {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestShouldTrackEvent(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 1,
+			BufferSize: "1Kb",
+			Timeout:    "1s",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me",
+				Code:        "abc",
+			},
+			{
+				PublisherId: "",
+				SiteAppId:   "track-me",
+				Code:        "abc",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	// no userExt
+	shouldTrack, code := logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			App: &openrtb2.App{
+				ID: "com.app.test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me-not",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.False(t, shouldTrack)
+	assert.Equal(t, "", code)
+
+	// no userExt
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				ID: "com.app.test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+		},
+	})
+
+	assert.False(t, shouldTrack)
+	assert.Equal(t, "", code)
+
+	// Constent: No agma
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				ID: "com.app.test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: "CP4LywcP4LywcLRAAAENCZCAAAIAAAIAAAAAIxQAQIwgAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A",
+			},
+		},
+	})
+
+	assert.False(t, shouldTrack)
+	assert.Equal(t, "", code)
+
+	// Constent: No Purpose 9
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				ID: "com.app.test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: "CP4LywcP4LywcLRAAAENCZCAAIAAAAAAAAAAIxQAQIxAAAAA.II7Nd_X__bX9n-_7_6ft0eY1f9_r37uQzDhfNs-8F3L_W_LwX32E7NF36tq4KmR4ku1bBIQNtHMnUDUmxaolVrzHsak2cpyNKJ_JkknsZe2dYGF9Pn9lD-YKZ7_5_9_f52T_9_9_-39z3_9f___dv_-__-vjf_599n_v9fV_78_Kf9______-____________8A",
+			},
+		},
+	})
+
+	assert.False(t, shouldTrack)
+	assert.Equal(t, "", code)
+
+	// No valid sites / apps / empty publisher app
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				ID: "",
+				Publisher: &openrtb2.Publisher{
+					ID: "",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.False(t, shouldTrack)
+	assert.Equal(t, "", code)
+
+	// should allow empty accounts
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				ID: "track-me",
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.True(t, shouldTrack)
+	assert.Equal(t, "abc", code)
+
+	// Bundle ID instead of app.id
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			App: &openrtb2.App{
+				Bundle: "track-me",
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.True(t, shouldTrack)
+	assert.Equal(t, "abc", code)
+}
+
+func TestShouldTrackMultipleAccounts(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 1,
+			BufferSize: "1Kb",
+			Timeout:    "1s",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me-a",
+				Code:        "abc",
+			},
+			{
+				PublisherId: "track-me-b",
+				Code:        "123",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	shouldTrack, code := logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			App: &openrtb2.App{
+				ID: "com.app.test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me-a",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.True(t, shouldTrack)
+	assert.Equal(t, "abc", code)
+
+	shouldTrack, code = logger.shouldTrackEvent(&openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			ID: "some-id",
+			Site: &openrtb2.Site{
+				ID: "site-test",
+				Publisher: &openrtb2.Publisher{
+					ID: "track-me-b",
+				},
+			},
+			User: &openrtb2.User{
+				Consent: agmaConsent,
+			},
+		},
+	})
+
+	assert.True(t, shouldTrack)
+	assert.Equal(t, "123", code)
+}
+
+func TestShouldNotTrackLog(t *testing.T) {
+	testCases := []struct {
+		name   string
+		config config.AgmaAnalytics
+	}{
+		{
+			name: "Test with do-not-track PublisherId",
+			config: config.AgmaAnalytics{
+				Enabled: true,
+				Endpoint: config.AgmaAnalyticsHttpEndpoint{
+					Url:     "http://localhost:8000/event",
+					Timeout: "5s",
+				},
+				Buffers: config.AgmaAnalyticsBuffer{
+					EventCount: 1,
+					BufferSize: "1Kb",
+					Timeout:    "1s",
+				},
+				Accounts: []config.AgmaAnalyticsAccount{
+					{
+						PublisherId: "do-not-track",
+						Code:        "abc",
+					},
+				},
+			},
+		},
+		{
+			name: "Test with do-not-track PublisherId",
+			config: config.AgmaAnalytics{
+				Enabled: true,
+				Endpoint: config.AgmaAnalyticsHttpEndpoint{
+					Url:     "http://localhost:8000/event",
+					Timeout: "5s",
+				},
+				Buffers: config.AgmaAnalyticsBuffer{
+					EventCount: 1,
+					BufferSize: "1Kb",
+					Timeout:    "1s",
+				},
+				Accounts: []config.AgmaAnalyticsAccount{
+					{
+						PublisherId: "track-me",
+						Code:        "abc",
+						SiteAppId:   "do-not-track",
+					},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			mockedSender := new(MockedSender)
+			mockedSender.On("Send", mock.Anything).Return(nil)
+			clockMock := clock.NewMock()
+			logger, err := newAgmaLogger(tc.config, mockedSender.Send, clockMock)
+			assert.NoError(t, err)
+
+			go logger.start()
+			assert.Zero(t, logger.eventCount)
+
+			logger.LogAuctionObject(&mockValidAuctionObject)
+			logger.LogVideoObject(&mockValidVideoObject)
+			logger.LogAmpObject(&mockValidAmpObject)
+
+			clockMock.Add(2 * time.Minute)
+			mockedSender.AssertNumberOfCalls(t, "Send", 0)
+			assert.Zero(t, logger.eventCount)
+		})
+	}
+}
+
+func TestRaceAllEvents(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 10000,
+			BufferSize: "100Mb",
+			Timeout:    "5m",
+		},
+		Accounts: mockValidAccounts,
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	go logger.start()
+
+	logger.LogAuctionObject(&mockValidAuctionObject)
+	logger.LogVideoObject(&mockValidVideoObject)
+	logger.LogAmpObject(&mockValidAmpObject)
+	clockMock.Add(10 * time.Millisecond)
+
+	logger.mux.RLock()
+	assert.Equal(t, int64(3), logger.eventCount)
+	logger.mux.RUnlock()
+}
+
+func TestFlushOnSigterm(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 10000,
+			BufferSize: "100Mb",
+			Timeout:    "5m",
+		},
+		Accounts: mockValidAccounts,
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	done := make(chan struct{})
+	go func() {
+		logger.start()
+		close(done)
+	}()
+
+	logger.LogAuctionObject(&mockValidAuctionObject)
+	logger.LogVideoObject(&mockValidVideoObject)
+	logger.LogAmpObject(&mockValidAmpObject)
+
+	logger.sigTermCh <- syscall.SIGTERM
+	<-done
+
+	time.Sleep(100 * time.Millisecond)
+
+	mockedSender.AssertCalled(t, "Send", mock.Anything)
+}
+
+func TestRaceBufferCount(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 2,
+			BufferSize: "100Mb",
+			Timeout:    "5m",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me",
+				Code:        "abc",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	go logger.start()
+	assert.Zero(t, logger.eventCount)
+
+	// Test EventCount Buffer
+	logger.LogAuctionObject(&mockValidAuctionObject)
+
+	clockMock.Add(1 * time.Millisecond)
+
+	logger.mux.RLock()
+	assert.Equal(t, int64(1), logger.eventCount)
+	logger.mux.RUnlock()
+
+	assert.Equal(t, false, logger.isFull())
+
+	// add 1 more
+	logger.LogAuctionObject(&mockValidAuctionObject)
+	clockMock.Add(1 * time.Millisecond)
+
+	// should trigger send and flash the buffer
+	mockedSender.AssertCalled(t, "Send", mock.Anything)
+
+	logger.mux.RLock()
+	assert.Equal(t, int64(0), logger.eventCount)
+	logger.mux.RUnlock()
+}
+
+func TestBufferSize(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 1000,
+			BufferSize: "20Kb",
+			Timeout:    "5m",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me",
+				Code:        "abc",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	go logger.start()
+
+	for i := 0; i < 50; i++ {
+		logger.LogAuctionObject(&mockValidAuctionObject)
+	}
+	clockMock.Add(10 * time.Millisecond)
+	mockedSender.AssertCalled(t, "Send", mock.Anything)
+	mockedSender.AssertNumberOfCalls(t, "Send", 1)
+}
+
+func TestBufferTime(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 1000,
+			BufferSize: "100mb",
+			Timeout:    "5m",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me",
+				Code:        "abc",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	go logger.start()
+
+	for i := 0; i < 5; i++ {
+		logger.LogAuctionObject(&mockValidAuctionObject)
+	}
+	clockMock.Add(10 * time.Minute)
+	mockedSender.AssertCalled(t, "Send", mock.Anything)
+	mockedSender.AssertNumberOfCalls(t, "Send", 1)
+}
+
+func TestRaceEnd2End(t *testing.T) {
+	var mu sync.Mutex
+
+	requestBodyAsString := ""
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Check for reponse
+		requestBody, err := io.ReadAll(r.Body)
+		mu.Lock()
+		requestBodyAsString = string(requestBody)
+		mu.Unlock()
+		if err != nil {
+			http.Error(w, "Error reading request body", 500)
+			return
+		}
+
+		w.WriteHeader(http.StatusOK)
+	}))
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     server.URL,
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 2,
+			BufferSize: "100mb",
+			Timeout:    "5m",
+		},
+		Accounts: mockValidAccounts,
+	}
+
+	clockMock := clock.NewMock()
+	clockMock.Set(time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC))
+
+	logger, err := NewModule(&http.Client{}, cfg, clockMock)
+	assert.NoError(t, err)
+
+	logger.LogAmpObject(&mockValidAmpObject)
+	logger.LogAmpObject(&mockValidAmpObject)
+
+	time.Sleep(250 * time.Millisecond)
+
+	expected := "[{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"},{\"type\":\"amp\",\"id\":\"some-id\",\"code\":\"abcd\",\"site\":{\"id\":\"track-me-site\",\"publisher\":{\"id\":\"track-me\"}},\"device\":{\"ua\":\"ua\"},\"user\":{\"consent\":\"" + agmaConsent + "\"},\"created_at\":\"2023-02-01T00:00:00Z\"}]"
+
+	mu.Lock()
+	actual := requestBodyAsString
+	mu.Unlock()
+
+	assert.Equal(t, expected, actual)
+}
+
+func TestShutdownFlush(t *testing.T) {
+	cfg := config.AgmaAnalytics{
+		Enabled: true,
+		Endpoint: config.AgmaAnalyticsHttpEndpoint{
+			Url:     "http://localhost:8000/event",
+			Timeout: "5s",
+		},
+		Buffers: config.AgmaAnalyticsBuffer{
+			EventCount: 1000,
+			BufferSize: "100mb",
+			Timeout:    "5m",
+		},
+		Accounts: []config.AgmaAnalyticsAccount{
+			{
+				PublisherId: "track-me",
+				Code:        "abc",
+			},
+		},
+	}
+	mockedSender := new(MockedSender)
+	mockedSender.On("Send", mock.Anything).Return(nil)
+	clockMock := clock.NewMock()
+	logger, err := newAgmaLogger(cfg, mockedSender.Send, clockMock)
+	assert.NoError(t, err)
+
+	go logger.start()
+	logger.LogAuctionObject(&mockValidAuctionObject)
+	logger.Shutdown()
+
+	time.Sleep(10 * time.Millisecond)
+
+	mockedSender.AssertCalled(t, "Send", mock.Anything)
+	mockedSender.AssertNumberOfCalls(t, "Send", 1)
+}
diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go
index 911de4c6959..6b16d0b7e92 100644
--- a/analytics/pubstack/pubstack_module_test.go
+++ b/analytics/pubstack/pubstack_module_test.go
@@ -99,7 +99,7 @@ func TestNewModuleSuccess(t *testing.T) {
 								{
 									ImpId:      "123",
 									StatusCode: 34,
-									Ext:        openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}},
+									Ext:        &openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}},
 								},
 							},
 						},
diff --git a/config/config.go b/config/config.go
index 05d6ca73fc8..aa132c4482e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -65,9 +65,9 @@ type Configuration struct {
 
 	VideoStoredRequestRequired bool `mapstructure:"video_stored_request_required"`
 
-	// Array of blacklisted apps that is used to create the hash table BlacklistedAppMap so App.ID's can be instantly accessed.
-	BlacklistedApps   []string `mapstructure:"blacklisted_apps,flow"`
-	BlacklistedAppMap map[string]bool
+	// Array of blocked apps that is used to create the hash table BlockedAppsLookup so App.ID's can be instantly accessed.
+	BlockedApps       []string `mapstructure:"blocked_apps,flow"`
+	BlockedAppsLookup map[string]bool
 	// Is publisher/account ID required to be submitted in the OpenRTB2 request
 	AccountRequired bool `mapstructure:"account_required"`
 	// AccountDefaults defines default settings for valid accounts that are partially defined
@@ -754,10 +754,10 @@ func New(v *viper.Viper, bidderInfos BidderInfos, normalizeBidderName func(strin
 	}
 
 	// To look for a request's app_id in O(1) time, we fill this hash table located in the
-	// the BlacklistedApps field of the Configuration struct defined in this file
-	c.BlacklistedAppMap = make(map[string]bool)
-	for i := 0; i < len(c.BlacklistedApps); i++ {
-		c.BlacklistedAppMap[c.BlacklistedApps[i]] = true
+	// the BlockedApps field of the Configuration struct defined in this file
+	c.BlockedAppsLookup = make(map[string]bool)
+	for i := 0; i < len(c.BlockedApps); i++ {
+		c.BlockedAppsLookup[c.BlockedApps[i]] = true
 	}
 
 	// Migrate combo stored request config to separate stored_reqs and amp stored_reqs configs.
@@ -1087,8 +1087,7 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
 	v.SetDefault("default_request.type", "")
 	v.SetDefault("default_request.file.name", "")
 	v.SetDefault("default_request.alias_info", false)
-	v.SetDefault("blacklisted_apps", []string{""})
-	v.SetDefault("blacklisted_accts", []string{""})
+	v.SetDefault("blocked_apps", []string{""})
 	v.SetDefault("account_required", false)
 	v.SetDefault("account_defaults.disabled", false)
 	v.SetDefault("account_defaults.debug_allow", true)
diff --git a/config/config_test.go b/config/config_test.go
index a551c1be66e..df0e9c8ce0d 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -435,7 +435,7 @@ metrics:
     adapter_connections_metrics: true
     adapter_gdpr_request_blocked: true
     account_modules_metrics: true
-blacklisted_apps: ["spamAppID","sketchy-app-id"]
+blocked_apps: ["spamAppID","sketchy-app-id"]
 account_required: true
 auto_gen_source_tid: false
 certificates_file: /etc/ssl/cert.pem
@@ -638,12 +638,12 @@ func TestFullConfig(t *testing.T) {
 	cmpBools(t, "lmt.enforce", true, cfg.LMT.Enforce)
 
 	//Assert the NonStandardPublishers was correctly unmarshalled
-	cmpStrings(t, "blacklisted_apps", "spamAppID", cfg.BlacklistedApps[0])
-	cmpStrings(t, "blacklisted_apps", "sketchy-app-id", cfg.BlacklistedApps[1])
+	cmpStrings(t, "blocked_apps", "spamAppID", cfg.BlockedApps[0])
+	cmpStrings(t, "blocked_apps", "sketchy-app-id", cfg.BlockedApps[1])
 
-	//Assert the BlacklistedAppMap hash table was built correctly
-	for i := 0; i < len(cfg.BlacklistedApps); i++ {
-		cmpBools(t, "cfg.BlacklistedAppMap", true, cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]])
+	//Assert the BlockedAppsLookup hash table was built correctly
+	for i := 0; i < len(cfg.BlockedApps); i++ {
+		cmpBools(t, "cfg.BlockedAppsLookup", true, cfg.BlockedAppsLookup[cfg.BlockedApps[i]])
 	}
 
 	//Assert purpose VendorExceptionMap hash tables were built correctly
diff --git a/docs/build/README.md b/docs/build/README.md
new file mode 100644
index 00000000000..0b52671f216
--- /dev/null
+++ b/docs/build/README.md
@@ -0,0 +1,110 @@
+## Overview
+
+As of v2.31.0, Prebid Server contains a module that requires CGo which introduces both build and runtime dependencies. To build, you need a C compiler, preferably gcc. To run, you may require one or more runtime dependencies, most notably libatomic.
+
+## Examples
+For a containerized example, see the Dockerfile.
+For manual build examples, including some cross-compilation use cases, see below.
+
+### From darwin amd64
+
+#### To darwin amd64
+`GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build`
+
+Running the built binary on mac amd64:
+`./prebid-server --stderrthreshold=WARNING -v=2`
+
+#### To darwin arm64
+`GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 go build`
+
+Running the built binary on mac arm64:
+`./prebid-server --stderrthreshold=WARNING -v=2`
+
+#### To windows amd64
+<b>Build</b>
+Install mingw-w64 which consists of a gcc compiler port you can use to generate windows binaries:
+`brew install mingw-w64`
+
+`GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="x86_64-w64-mingw32-gcc" go build`
+
+<b>Run</b>
+Running the built binary on windows:
+`.\prebid-server.exe --sderrthreshold=WARNING =v=2`
+
+You may receive the following errors or something similar:
+```
+"The code execution cannot proceed because libatomic-1.dll was not found."
+"The code execution cannot proceed because libwinpthread-1.dll was not found."
+```
+
+To resolve these errors, copy the following files from mingw-64 on your mac to `C:/windows/System32` and re-run:
+`/usr/local/Cellar/mingw-w64/12.0.0_1/toolchain-x86_64/x86_64-w64-mingw32/lib/libatomic-1.dll`
+`/usr/local/Cellar/mingw-w64/12.0.0_1/toolchain-x86_64/x86_64-w64-mingw32/bin/libwinpthread-1.dll`
+
+### From windows amd64
+#### To windows amd64
+<b>Build</b>
+`set CGO_ENABLED=1`
+`set GOOS=windows`
+`set GOARCH=amd64`
+`go build . && .\prebid-server.exe --stderrthreshold=WARNING -v=2`
+
+You may receive the following error or something similar:
+```
+# runtime/cgo
+cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in %PATH%
+```
+
+To resolve the error, install MSYS2:
+1) Download the installer (https://www.msys2.org/)
+2) Run the installer and follow the steps of the installation wizard
+3) Run MSYS2 which will open an MSYS2 terminal for you
+4) In the MSYS2 terminal, install windows/amd64 gcc toolchain: `pacman -S --needed base-devel mingw-w64-x86_64-gcc`
+5) Enter `Y` when prompted whether to proceed with the installation
+6) Add the path of your MinGW-w64 `bin` folder to the Windows `PATH` environment variable by using the following steps:
+    - In the Windows search bar, type <b>Settings</b> to open your Windows Settings.
+    - Search for <b>Edit environment variables for your account</b>.
+    - In your <b>User variables</b>, select the `Path` variable and then select <b>Edit</b>.
+    - Select </b>New and add the MinGW-w64 destination folder you recorded during the installation process to the list. If you used the default settings above, then this will be the path: `C:\msys64\ucrt64\bin`.
+    - Select <b>OK</b>, and then select <b>OK</b> again in the <b>Environment Variables</b> window to update the `PATH` environment variable. You have to reopen any console windows for the updated `PATH` environment variable to be available.
+7) Confirm gcc installed: `gcc --version`
+
+<b>Run</b>
+Running the built binary on windows:
+`go build . && .\prebid-server.exe --stderrthreshold=WARNING -v=2`
+
+You may receive the following errors or something similar:
+```
+"The code execution cannot proceed because libatomic-1.dll was not found."
+"The code execution cannot proceed because libwinpthread-1.dll was not found."
+```
+To resolve these errors, copy the following files from MSYS2 installation to `C:/windows/System32` and re-run:
+`C:\mysys64\mingw64\bin\libatomic-1.dll`
+`C:\mysys64\mingw64\bin\libwinpthread-1.dll`
+
+### From linux amd64
+#### To linux amd64
+<b>Note</b>
+These instructions are for building and running on Debian-based distributions
+
+<b>Build</b>
+`GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build`
+
+You may receive the following error or something similar:
+```
+# runtime/cgo
+cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in $PATH
+```
+To resolve the error, install gcc and re-build:
+`sudo apt-get install -y gcc`
+
+<b>Run</b>
+Running the built binary on Linux:
+`./prebid-server --stderrthreshold=WARNING -v=2`
+
+You may receive the following error or something similar:
+```
+... error while loading shared libraries: libatomic.so.1: cannot open shared object file ...
+```
+To resolve the error, install libatomic1 and re-run:
+`sudo apt-get install -y libatomic1`
\ No newline at end of file
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 84d6d7847ef..b691b2c0084 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math"
 	"net/http"
 	"strconv"
 	"strings"
@@ -167,6 +168,11 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma
 	tcf2Cfg := c.privacyConfig.tcf2ConfigBuilder(c.privacyConfig.gdprConfig.TCF2, account.GDPR)
 	gdprPerms := c.privacyConfig.gdprPermissionsBuilder(tcf2Cfg, gdprRequestInfo)
 
+	limit := math.MaxInt
+	if request.Limit != nil {
+		limit = *request.Limit
+	}
+
 	rx := usersync.Request{
 		Bidders: request.Bidders,
 		Cooperative: usersync.Cooperative{
@@ -174,7 +180,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma
 			PriorityGroups: c.config.UserSync.PriorityGroups,
 		},
 		Debug: request.Debug,
-		Limit: request.Limit,
+		Limit: limit,
 		Privacy: usersyncPrivacy{
 			gdprPermissions:  gdprPerms,
 			ccpaParsedPolicy: ccpaParsedPolicy,
@@ -278,17 +284,38 @@ func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) {
 }
 
 func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest {
-	if request.Limit <= 0 && cookieSyncConfig.DefaultLimit != nil {
-		request.Limit = *cookieSyncConfig.DefaultLimit
+	limit := getEffectiveLimit(request.Limit, cookieSyncConfig.DefaultLimit)
+	maxLimit := getEffectiveMaxLimit(cookieSyncConfig.MaxLimit)
+	if maxLimit < limit {
+		request.Limit = &maxLimit
+	} else {
+		request.Limit = &limit
 	}
-	if cookieSyncConfig.MaxLimit != nil && (request.Limit <= 0 || request.Limit > *cookieSyncConfig.MaxLimit) {
-		request.Limit = *cookieSyncConfig.MaxLimit
+	return request
+}
+
+func getEffectiveLimit(reqLimit *int, defaultLimit *int) int {
+	limit := reqLimit
+
+	if limit == nil {
+		limit = defaultLimit
 	}
-	if request.Limit < 0 {
-		request.Limit = 0
+
+	if limit != nil && *limit > 0 {
+		return *limit
 	}
 
-	return request
+	return math.MaxInt
+}
+
+func getEffectiveMaxLimit(maxLimit *int) int {
+	limit := maxLimit
+
+	if limit != nil && *limit > 0 {
+		return *limit
+	}
+
+	return math.MaxInt
 }
 
 func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest {
@@ -395,8 +422,8 @@ func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.Bidd
 			c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked)
 		case usersync.StatusAlreadySynced:
 			c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced)
-		case usersync.StatusTypeNotSupported:
-			c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncTypeNotSupported)
+		case usersync.StatusRejectedByFilter:
+			c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncRejectedByFilter)
 		}
 	}
 }
@@ -490,8 +517,8 @@ func getDebugMessage(status usersync.Status) string {
 		return "Unsupported bidder"
 	case usersync.StatusUnconfiguredBidder:
 		return "No sync config"
-	case usersync.StatusTypeNotSupported:
-		return "Type not supported"
+	case usersync.StatusRejectedByFilter:
+		return "Rejected by request filter"
 	case usersync.StatusBlockedByDisabledUsersync:
 		return "Sync disabled by config"
 	}
@@ -503,7 +530,7 @@ type cookieSyncRequest struct {
 	GDPR            *int                             `json:"gdpr"`
 	GDPRConsent     string                           `json:"gdpr_consent"`
 	USPrivacy       string                           `json:"us_privacy"`
-	Limit           int                              `json:"limit"`
+	Limit           *int                             `json:"limit"`
 	GPP             string                           `json:"gpp"`
 	GPPSID          string                           `json:"gpp_sid"`
 	CooperativeSync *bool                            `json:"coopSync"`
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index adfdcb22fab..bdc57322bf7 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"io"
+	"math"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -631,6 +632,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 			givenCCPAEnabled: true,
 			expectedPrivacy:  macros.UserSyncPrivacy{},
 			expectedRequest: usersync.Request{
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -659,6 +661,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        true,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -687,6 +690,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        false,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -715,6 +719,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        false,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -743,6 +748,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        false,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -771,6 +777,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        true,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -799,6 +806,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 					Enabled:        true,
 					PriorityGroups: [][]string{{"a", "b", "c"}},
 				},
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -817,6 +825,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 			givenCCPAEnabled: true,
 			expectedPrivacy:  macros.UserSyncPrivacy{},
 			expectedRequest: usersync.Request{
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -837,6 +846,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 				USPrivacy: "1NYN",
 			},
 			expectedRequest: usersync.Request{
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -878,6 +888,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 				GDPR: "0",
 			},
 			expectedRequest: usersync.Request{
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -905,6 +916,7 @@ func TestCookieSyncParseRequest(t *testing.T) {
 				GDPR: "",
 			},
 			expectedRequest: usersync.Request{
+				Limit: math.MaxInt,
 				Privacy: usersyncPrivacy{
 					gdprPermissions: &fakePermissions{},
 					activityRequest: emptyActivityPoliciesRequest,
@@ -1074,152 +1086,242 @@ func TestCookieSyncParseRequest(t *testing.T) {
 	}
 }
 
-func TestSetLimit(t *testing.T) {
-	intNegative1 := -1
-	int20 := 20
-	int30 := 30
-	int40 := 40
+func TestGetEffectiveLimit(t *testing.T) {
+	intNegative := ptrutil.ToPtr(-1)
+	int0 := ptrutil.ToPtr(0)
+	int30 := ptrutil.ToPtr(30)
+	int40 := ptrutil.ToPtr(40)
+	intMax := ptrutil.ToPtr(math.MaxInt)
+
+	tests := []struct {
+		name          string
+		reqLimit      *int
+		defaultLimit  *int
+		expectedLimit int
+	}{
+		{
+			name:          "nil",
+			reqLimit:      nil,
+			defaultLimit:  nil,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "req_limit_negative",
+			reqLimit:      intNegative,
+			defaultLimit:  nil,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "req_limit_zero",
+			reqLimit:      int0,
+			defaultLimit:  nil,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "req_limit_in_range",
+			reqLimit:      int30,
+			defaultLimit:  nil,
+			expectedLimit: 30,
+		},
+		{
+			name:          "req_limit_at_max",
+			reqLimit:      intMax,
+			defaultLimit:  nil,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "default_limit_negative",
+			reqLimit:      nil,
+			defaultLimit:  intNegative,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "default_limit_zero",
+			reqLimit:      nil,
+			defaultLimit:  intNegative,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "default_limit_in_range",
+			reqLimit:      nil,
+			defaultLimit:  int30,
+			expectedLimit: 30,
+		},
+		{
+			name:          "default_limit_at_max",
+			reqLimit:      nil,
+			defaultLimit:  intMax,
+			expectedLimit: math.MaxInt,
+		},
+		{
+			name:          "both_in_range",
+			reqLimit:      int30,
+			defaultLimit:  int40,
+			expectedLimit: 30,
+		},
+	}
 
-	testCases := []struct {
-		description     string
-		givenRequest    cookieSyncRequest
-		givenAccount    *config.Account
-		expectedRequest cookieSyncRequest
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result := getEffectiveLimit(test.reqLimit, test.defaultLimit)
+			assert.Equal(t, test.expectedLimit, result)
+		})
+	}
+}
+
+func TestGetEffectiveMaxLimit(t *testing.T) {
+	intNegative := ptrutil.ToPtr(-1)
+	int0 := ptrutil.ToPtr(0)
+	int30 := ptrutil.ToPtr(30)
+	intMax := ptrutil.ToPtr(math.MaxInt)
+
+	tests := []struct {
+		name          string
+		maxLimit      *int
+		expectedLimit int
 	}{
 		{
-			description: "Default Limit is Applied (request limit = 0)",
-			givenRequest: cookieSyncRequest{
-				Limit: 0,
-			},
-			givenAccount: &config.Account{
-				CookieSync: config.CookieSync{
-					DefaultLimit: &int20,
-				},
-			},
-			expectedRequest: cookieSyncRequest{
-				Limit: 20,
-			},
+			name:          "nil",
+			maxLimit:      nil,
+			expectedLimit: math.MaxInt,
 		},
 		{
-			description: "Default Limit is Not Applied (default limit not set)",
-			givenRequest: cookieSyncRequest{
-				Limit: 0,
-			},
-			givenAccount: &config.Account{
-				CookieSync: config.CookieSync{
-					DefaultLimit: nil,
-				},
-			},
-			expectedRequest: cookieSyncRequest{
-				Limit: 0,
-			},
+			name:          "req_limit_negative",
+			maxLimit:      intNegative,
+			expectedLimit: math.MaxInt,
 		},
 		{
-			description: "Default Limit is Not Applied (request limit > 0)",
-			givenRequest: cookieSyncRequest{
-				Limit: 10,
-			},
-			givenAccount: &config.Account{
-				CookieSync: config.CookieSync{
-					DefaultLimit: &int20,
-				},
-			},
-			expectedRequest: cookieSyncRequest{
-				Limit: 10,
-			},
+			name:          "req_limit_zero",
+			maxLimit:      int0,
+			expectedLimit: math.MaxInt,
 		},
 		{
-			description: "Max Limit is Applied (request limit <= 0)",
+			name:          "req_limit_in_range",
+			maxLimit:      int30,
+			expectedLimit: 30,
+		},
+		{
+			name:          "req_limit_too_large",
+			maxLimit:      intMax,
+			expectedLimit: math.MaxInt,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result := getEffectiveMaxLimit(test.maxLimit)
+			assert.Equal(t, test.expectedLimit, result)
+		})
+	}
+}
+
+func TestSetLimit(t *testing.T) {
+	intNegative := ptrutil.ToPtr(-1)
+	int0 := ptrutil.ToPtr(0)
+	int10 := ptrutil.ToPtr(10)
+	int20 := ptrutil.ToPtr(20)
+	int30 := ptrutil.ToPtr(30)
+	intMax := ptrutil.ToPtr(math.MaxInt)
+
+	tests := []struct {
+		name            string
+		givenRequest    cookieSyncRequest
+		givenAccount    *config.Account
+		expectedRequest cookieSyncRequest
+	}{
+		{
+			name: "nil_limits",
 			givenRequest: cookieSyncRequest{
-				Limit: 0,
+				Limit: nil,
 			},
 			givenAccount: &config.Account{
 				CookieSync: config.CookieSync{
-					MaxLimit: &int30,
+					DefaultLimit: nil,
+					MaxLimit:     nil,
 				},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 30,
+				Limit: intMax,
 			},
 		},
 		{
-			description: "Max Limit is Applied (0 < max < limit)",
+			name: "limit_negative",
 			givenRequest: cookieSyncRequest{
-				Limit: 40,
+				Limit: intNegative,
 			},
 			givenAccount: &config.Account{
 				CookieSync: config.CookieSync{
-					MaxLimit: &int30,
+					DefaultLimit: int20,
 				},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 30,
+				Limit: intMax,
 			},
 		},
 		{
-			description: "Max Limit is Not Applied (max not set)",
+			name: "limit_zero",
 			givenRequest: cookieSyncRequest{
-				Limit: 10,
+				Limit: int0,
 			},
 			givenAccount: &config.Account{
 				CookieSync: config.CookieSync{
-					MaxLimit: nil,
+					DefaultLimit: int20,
 				},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 10,
+				Limit: intMax,
 			},
 		},
 		{
-			description: "Max Limit is Not Applied (0 < limit < max)",
+			name: "limit_less_than_max",
 			givenRequest: cookieSyncRequest{
-				Limit: 10,
+				Limit: int10,
 			},
 			givenAccount: &config.Account{
 				CookieSync: config.CookieSync{
-					MaxLimit: &int30,
+					DefaultLimit: int20,
+					MaxLimit:     int30,
 				},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 10,
+				Limit: int10,
 			},
 		},
 		{
-			description: "Max Limit is Applied After applying the default",
+			name: "limit_greater_than_max",
 			givenRequest: cookieSyncRequest{
-				Limit: 0,
+				Limit: int30,
 			},
 			givenAccount: &config.Account{
 				CookieSync: config.CookieSync{
-					DefaultLimit: &int40,
-					MaxLimit:     &int30,
+					DefaultLimit: int20,
+					MaxLimit:     int10,
 				},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 30,
+				Limit: int10,
 			},
 		},
 		{
-			description: "Negative Value Check",
+			name: "limit_at_max",
 			givenRequest: cookieSyncRequest{
-				Limit: 0,
+				Limit: intMax,
 			},
 			givenAccount: &config.Account{
-				CookieSync: config.CookieSync{
-					DefaultLimit: &intNegative1,
-					MaxLimit:     &intNegative1,
-				},
+				CookieSync: config.CookieSync{},
 			},
 			expectedRequest: cookieSyncRequest{
-				Limit: 0,
+				Limit: intMax,
 			},
 		},
 	}
 
-	for _, test := range testCases {
-		endpoint := cookieSyncEndpoint{}
-		request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync)
-		assert.Equal(t, test.expectedRequest, request, test.description)
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			endpoint := cookieSyncEndpoint{}
+			request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync)
+			assert.Equal(t, test.expectedRequest, request)
+		})
 	}
 }
 
@@ -1581,10 +1683,10 @@ func TestCookieSyncWriteBidderMetrics(t *testing.T) {
 			},
 		},
 		{
-			description: "One - Type Not Supported",
-			given:       []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusTypeNotSupported}},
+			description: "One - Rejected By Filter",
+			given:       []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusRejectedByFilter}},
 			setExpectations: func(m *metrics.MetricsEngineMock) {
-				m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncTypeNotSupported).Once()
+				m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncRejectedByFilter).Once()
 			},
 		},
 		{
@@ -1638,7 +1740,7 @@ func TestCookieSyncHandleResponse(t *testing.T) {
 		{Bidder: "Bidder2", Status: usersync.StatusUnknownBidder},
 		{Bidder: "Bidder3", Status: usersync.StatusUnconfiguredBidder},
 		{Bidder: "Bidder4", Status: usersync.StatusBlockedByPrivacy},
-		{Bidder: "Bidder5", Status: usersync.StatusTypeNotSupported},
+		{Bidder: "Bidder5", Status: usersync.StatusRejectedByFilter},
 		{Bidder: "Bidder6", Status: usersync.StatusBlockedByUserOptOut},
 		{Bidder: "Bidder7", Status: usersync.StatusBlockedByDisabledUsersync},
 		{Bidder: "BidderA", Status: usersync.StatusDuplicate, SyncerKey: "syncerB"},
@@ -1731,7 +1833,7 @@ func TestCookieSyncHandleResponse(t *testing.T) {
 			givenCookieHasSyncs: true,
 			givenDebug:          true,
 			givenSyncersChosen:  []usersync.SyncerChoice{},
-			expectedJSON:        `{"status":"ok","bidder_status":[],"debug":[{"bidder":"Bidder1","error":"Already in sync"},{"bidder":"Bidder2","error":"Unsupported bidder"},{"bidder":"Bidder3","error":"No sync config"},{"bidder":"Bidder4","error":"Rejected by privacy"},{"bidder":"Bidder5","error":"Type not supported"},{"bidder":"Bidder6","error":"Status blocked by user opt out"},{"bidder":"Bidder7","error":"Sync disabled by config"},{"bidder":"BidderA","error":"Duplicate bidder synced as syncerB"}]}` + "\n",
+			expectedJSON:        `{"status":"ok","bidder_status":[],"debug":[{"bidder":"Bidder1","error":"Already in sync"},{"bidder":"Bidder2","error":"Unsupported bidder"},{"bidder":"Bidder3","error":"No sync config"},{"bidder":"Bidder4","error":"Rejected by privacy"},{"bidder":"Bidder5","error":"Rejected by request filter"},{"bidder":"Bidder6","error":"Status blocked by user opt out"},{"bidder":"Bidder7","error":"Sync disabled by config"},{"bidder":"BidderA","error":"Duplicate bidder synced as syncerB"}]}` + "\n",
 			expectedAnalytics:   analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}},
 		},
 	}
@@ -2151,9 +2253,9 @@ func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ex
 	return args.Bool(0), args.Error(1)
 }
 
-func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
+func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
 	args := m.Called(ctx, bidderCoreName, bidder)
-	return args.Get(0).(gdpr.AuctionPermissions), args.Error(1)
+	return args.Get(0).(gdpr.AuctionPermissions)
 }
 
 type FakeAccountsFetcher struct {
@@ -2183,10 +2285,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
+func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
 	return gdpr.AuctionPermissions{
 		AllowBidRequest: true,
-	}, nil
+	}
 }
 
 func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
diff --git a/endpoints/events/event.go b/endpoints/events/event.go
index b92b72f17ad..6a4dd8b7096 100644
--- a/endpoints/events/event.go
+++ b/endpoints/events/event.go
@@ -216,7 +216,7 @@ func HandleAccountServiceErrors(errs []error) (status int, messages []string) {
 
 		errCode := errortypes.ReadCode(er)
 
-		if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode {
+		if errCode == errortypes.BlockedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode {
 			status = http.StatusServiceUnavailable
 		}
 		if errCode == errortypes.MalformedAcctErrorCode {
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index a6ad8d3fc65..e8e135acd39 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -59,7 +59,7 @@ type ORTB2 struct {
 func NewAmpEndpoint(
 	uuidGenerator uuidutil.UUIDGenerator,
 	ex exchange.Exchange,
-	validator openrtb_ext.BidderParamValidator,
+	requestValidator ortb.RequestValidator,
 	requestsById stored_requests.Fetcher,
 	accounts stored_requests.AccountFetcher,
 	cfg *config.Configuration,
@@ -73,7 +73,7 @@ func NewAmpEndpoint(
 	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
 
-	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
+	if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
 		return nil, errors.New("NewAmpEndpoint requires non-nil arguments.")
 	}
 
@@ -87,7 +87,7 @@ func NewAmpEndpoint(
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
-		validator,
+		requestValidator,
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
 		accounts,
@@ -156,6 +156,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	w.Header().Set("AMP-Access-Control-Allow-Source-Origin", origin)
 	w.Header().Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin")
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
+	setBrowsingTopicsHeader(w, r)
 
 	// There is no body for AMP requests, so we pass a nil body and ignore the return value.
 	_, rejectErr := hookExecutor.ExecuteEntrypointStage(r, nilBody)
@@ -171,7 +172,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 	if errortypes.ContainsFatalError(errL) {
 		w.WriteHeader(http.StatusBadRequest)
 		for _, err := range errortypes.FatalOnly(errL) {
-			w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
+			fmt.Fprintf(w, "Invalid request: %s\n", err.Error())
 		}
 		labels.RequestStatus = metrics.RequestStatusBadInput
 		return
@@ -210,9 +211,9 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		metricsStatus := metrics.RequestStatusBadInput
 		for _, er := range errL {
 			errCode := errortypes.ReadCode(er)
-			if errCode == errortypes.BlacklistedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode {
+			if errCode == errortypes.BlockedAppErrorCode || errCode == errortypes.AccountDisabledErrorCode {
 				httpStatus = http.StatusServiceUnavailable
-				metricsStatus = metrics.RequestStatusBlacklisted
+				metricsStatus = metrics.RequestStatusBlockedApp
 				break
 			}
 			if errCode == errortypes.MalformedAcctErrorCode {
@@ -224,12 +225,30 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		w.WriteHeader(httpStatus)
 		labels.RequestStatus = metricsStatus
 		for _, err := range errortypes.FatalOnly(errL) {
-			w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
+			fmt.Fprintf(w, "Invalid request: %s\n", err.Error())
 		}
 		ao.Errors = append(ao.Errors, acctIDErrs...)
 		return
 	}
 
+	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
+	if errs := deps.setFieldsImplicitly(r, reqWrapper, account); len(errs) > 0 {
+		errL = append(errL, errs...)
+	}
+
+	hasStoredAuctionResponses := len(storedAuctionResponses) > 0
+	errs := deps.validateRequest(account, r, reqWrapper, true, hasStoredAuctionResponses, storedBidResponses, false)
+	errL = append(errL, errs...)
+	ao.Errors = append(ao.Errors, errs...)
+	if errortypes.ContainsFatalError(errs) {
+		w.WriteHeader(http.StatusBadRequest)
+		for _, err := range errortypes.FatalOnly(errs) {
+			fmt.Fprintf(w, "Invalid request: %s\n", err.Error())
+		}
+		labels.RequestStatus = metrics.RequestStatusBadInput
+		return
+	}
+
 	tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
 
 	activityControl = privacy.NewActivityControl(&account.Privacy)
@@ -385,8 +404,13 @@ func sendAmpResponse(
 	ao.AmpTargetingValues = targets
 
 	// Fixes #231
-	enc := json.NewEncoder(w)
+	enc := json.NewEncoder(w) // nosemgrep: json-encoder-needs-type
 	enc.SetEscapeHTML(false)
+	// Explicitly set content type to text/plain, which had previously been
+	// the implied behavior from the time the project was launched.
+	// It's unclear why text/plain was chosen or if it was an oversight,
+	// nevertheless we will keep it as such for compatibility reasons.
+	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
 
 	// If an error happens when encoding the response, there isn't much we can do.
 	// If we've sent _any_ bytes, then Go would have sent the 200 status code first.
@@ -423,6 +447,9 @@ func getExtBidResponse(
 		warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)
 	}
 	for _, v := range errortypes.WarningOnly(errs) {
+		if errortypes.ReadScope(v) == errortypes.ScopeDebug && !(reqWrapper != nil && reqWrapper.Test == 1) {
+			continue
+		}
 		bidderErr := openrtb_ext.ExtBidderMessage{
 			Code:    errortypes.ReadCode(v),
 			Message: v.Error(),
@@ -483,8 +510,13 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr
 	// move to using the request wrapper
 	req = &openrtb_ext.RequestWrapper{BidRequest: reqNormal}
 
-	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
-	deps.setFieldsImplicitly(httpRequest, req)
+	// normalize to openrtb 2.6
+	if err := openrtb_ext.ConvertUpTo26(req); err != nil {
+		errs = append(errs, err)
+	}
+	if errortypes.ContainsFatalError(errs) {
+		return
+	}
 
 	// Need to ensure cache and targeting are turned on
 	e = initAmpTargetingAndCache(req)
@@ -497,10 +529,6 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr
 		return
 	}
 
-	hasStoredResponses := len(storedAuctionResponses) > 0
-	e = deps.validateRequest(req, true, hasStoredResponses, storedBidResponses, false)
-	errs = append(errs, e...)
-
 	return
 }
 
@@ -514,7 +542,7 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req
 		return nil, nil, nil, nil, []error{err}
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond)
 	defer cancel()
 
 	storedRequests, _, errs := deps.storedReqFetcher.FetchRequests(ctx, []string{ampParams.StoredRequestID}, nil)
diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go
index 7d87b65301d..e3533589859 100644
--- a/endpoints/openrtb2/amp_auction_test.go
+++ b/endpoints/openrtb2/amp_auction_test.go
@@ -10,6 +10,7 @@ import (
 	"net/url"
 	"os"
 	"strconv"
+	"strings"
 	"testing"
 	"time"
 
@@ -30,6 +31,7 @@ import (
 	"github.com/prebid/prebid-server/v2/metrics"
 	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/ortb"
 	"github.com/prebid/prebid-server/v2/privacy"
 	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
 	"github.com/prebid/prebid-server/v2/util/jsonutil"
@@ -54,6 +56,7 @@ func TestGoodAmpRequests(t *testing.T) {
 				"buyeruids-case-insensitive.json",
 				"buyeruids-camel-case.json",
 				"aliased-buyeruids-case-insensitive.json",
+				"ortb-2.5-to-2.6-upconvert.json",
 			},
 		},
 		{
@@ -101,8 +104,8 @@ func TestGoodAmpRequests(t *testing.T) {
 				GDPR:           config.GDPR{Enabled: true},
 			}
 			if test.Config != nil {
-				cfg.BlacklistedApps = test.Config.BlacklistedApps
-				cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
+				cfg.BlockedApps = test.Config.BlockedApps
+				cfg.BlockedAppsLookup = test.Config.getBlockedAppLookup()
 				cfg.AccountRequired = test.Config.AccountRequired
 			}
 
@@ -136,6 +139,17 @@ func TestGoodAmpRequests(t *testing.T) {
 					assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename)
 				}
 			}
+			if test.ExpectedMockBidderRequests != nil {
+				for bidder, req := range test.ExpectedMockBidderRequests {
+					a, ok := ex.adapters[openrtb_ext.BidderName(bidder)]
+					if !ok {
+						t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename)
+					}
+					aa := a.(*exchange.BidderAdapter)
+					ma := aa.Bidder.(*mockAdapter)
+					assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename)
+				}
+			}
 		}
 	}
 }
@@ -201,7 +215,7 @@ func TestAMPPageInfo(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -233,55 +247,47 @@ func TestGDPRConsent(t *testing.T) {
 	existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
 
 	testCases := []struct {
-		description     string
-		consent         string
-		userExt         *openrtb_ext.ExtUser
-		nilUser         bool
-		expectedUserExt openrtb_ext.ExtUser
+		description  string
+		consent      string
+		user         *openrtb2.User
+		nilUser      bool
+		expectedUser *openrtb2.User
 	}{
 		{
 			description: "Nil User",
 			consent:     consent,
 			nilUser:     true,
-			expectedUserExt: openrtb_ext.ExtUser{
-				Consent: consent,
-			},
-		},
-		{
-			description: "Nil User Ext",
-			consent:     consent,
-			userExt:     nil,
-			expectedUserExt: openrtb_ext.ExtUser{
+			expectedUser: &openrtb2.User{
 				Consent: consent,
 			},
 		},
 		{
 			description: "Overrides Existing Consent",
 			consent:     consent,
-			userExt: &openrtb_ext.ExtUser{
+			user: &openrtb2.User{
 				Consent: existingConsent,
 			},
-			expectedUserExt: openrtb_ext.ExtUser{
+			expectedUser: &openrtb2.User{
 				Consent: consent,
 			},
 		},
 		{
 			description: "Overrides Existing Consent - With Sibling Data",
 			consent:     consent,
-			userExt: &openrtb_ext.ExtUser{
+			user: &openrtb2.User{
 				Consent: existingConsent,
 			},
-			expectedUserExt: openrtb_ext.ExtUser{
+			expectedUser: &openrtb2.User{
 				Consent: consent,
 			},
 		},
 		{
 			description: "Does Not Override Existing Consent If Empty",
 			consent:     "",
-			userExt: &openrtb_ext.ExtUser{
+			user: &openrtb2.User{
 				Consent: existingConsent,
 			},
-			expectedUserExt: openrtb_ext.ExtUser{
+			expectedUser: &openrtb2.User{
 				Consent: existingConsent,
 			},
 		},
@@ -289,7 +295,7 @@ func TestGDPRConsent(t *testing.T) {
 
 	for _, test := range testCases {
 		// Build Request
-		bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil)
+		bid, err := getTestBidRequest(test.nilUser, test.user, true, nil)
 		if err != nil {
 			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
 		}
@@ -299,10 +305,11 @@ func TestGDPRConsent(t *testing.T) {
 
 		// Build Exchange Endpoint
 		mockExchange := &mockAmpExchange{}
+
 		endpoint, _ := NewAmpEndpoint(
 			fakeUUIDGenerator{},
 			mockExchange,
-			newParamsValidator(t),
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{
@@ -338,15 +345,8 @@ func TestGDPRConsent(t *testing.T) {
 		if !assert.NotNil(t, result.User, test.description+":lastRequest.User") {
 			return
 		}
-		if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") {
-			return
-		}
-		var ue openrtb_ext.ExtUser
-		err = jsonutil.UnmarshalValid(result.User.Ext, &ue)
-		if !assert.NoError(t, err, test.description+":deserialize") {
-			return
-		}
-		assert.Equal(t, test.expectedUserExt, ue, test.description)
+
+		assert.Equal(t, test.expectedUser, result.User, test.description)
 		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors, test.description+":errors")
 		assert.Empty(t, response.ORTB2.Ext.Warnings, test.description+":warnings")
 
@@ -369,15 +369,8 @@ func TestGDPRConsent(t *testing.T) {
 		if !assert.NotNil(t, resultLegacy.User, test.description+":legacy:lastRequest.User") {
 			return
 		}
-		if !assert.NotNil(t, resultLegacy.User.Ext, test.description+":legacy:lastRequest.User.Ext") {
-			return
-		}
-		var ueLegacy openrtb_ext.ExtUser
-		err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy)
-		if !assert.NoError(t, err, test.description+":legacy:deserialize") {
-			return
-		}
-		assert.Equal(t, test.expectedUserExt, ueLegacy, test.description+":legacy")
+
+		assert.Equal(t, test.expectedUser, resultLegacy.User, test.description+":legacy")
 		assert.Equal(t, expectedErrorsFromHoldAuction, responseLegacy.ORTB2.Ext.Errors, test.description+":legacy:errors")
 		assert.Empty(t, responseLegacy.ORTB2.Ext.Warnings, test.description+":legacy:warnings")
 	}
@@ -550,28 +543,6 @@ func TestOverrideWithParams(t *testing.T) {
 				errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"},
 			},
 		},
-		{
-			desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error",
-			given: testInput{
-				ampParams: amp.Params{
-					ConsentType: amp.ConsentTCF2,
-					Consent:     "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
-				},
-				bidRequest: &openrtb2.BidRequest{
-					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
-					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
-				},
-			},
-			expected: testOutput{
-				bidRequest: &openrtb2.BidRequest{
-					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
-					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
-					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
-				},
-				errorMsgs:         []string{"expect \" after {, but found m"},
-				expectFatalErrors: true,
-			},
-		},
 	}
 
 	for _, test := range testCases {
@@ -658,46 +629,46 @@ func TestCCPAConsent(t *testing.T) {
 	var gdpr int8 = 1
 
 	testCases := []struct {
-		description    string
-		consent        string
-		regsExt        *openrtb_ext.ExtRegs
-		nilRegs        bool
-		expectedRegExt openrtb_ext.ExtRegs
+		description string
+		consent     string
+		regs        openrtb2.Regs
+		nilRegs     bool
+		expectedReg *openrtb2.Regs
 	}{
 		{
 			description: "Nil Regs",
 			consent:     consent,
 			nilRegs:     true,
-			expectedRegExt: openrtb_ext.ExtRegs{
+			expectedReg: &openrtb2.Regs{
 				USPrivacy: consent,
 			},
 		},
 		{
 			description: "Nil Regs Ext",
 			consent:     consent,
-			regsExt:     nil,
-			expectedRegExt: openrtb_ext.ExtRegs{
+			nilRegs:     true,
+			expectedReg: &openrtb2.Regs{
 				USPrivacy: consent,
 			},
 		},
 		{
 			description: "Overrides Existing Consent",
 			consent:     consent,
-			regsExt: &openrtb_ext.ExtRegs{
+			regs: openrtb2.Regs{
 				USPrivacy: existingConsent,
 			},
-			expectedRegExt: openrtb_ext.ExtRegs{
+			expectedReg: &openrtb2.Regs{
 				USPrivacy: consent,
 			},
 		},
 		{
 			description: "Overrides Existing Consent - With Sibling Data",
 			consent:     consent,
-			regsExt: &openrtb_ext.ExtRegs{
+			regs: openrtb2.Regs{
 				USPrivacy: existingConsent,
 				GDPR:      &gdpr,
 			},
-			expectedRegExt: openrtb_ext.ExtRegs{
+			expectedReg: &openrtb2.Regs{
 				USPrivacy: consent,
 				GDPR:      &gdpr,
 			},
@@ -705,10 +676,10 @@ func TestCCPAConsent(t *testing.T) {
 		{
 			description: "Does Not Override Existing Consent If Empty",
 			consent:     "",
-			regsExt: &openrtb_ext.ExtRegs{
+			regs: openrtb2.Regs{
 				USPrivacy: existingConsent,
 			},
-			expectedRegExt: openrtb_ext.ExtRegs{
+			expectedReg: &openrtb2.Regs{
 				USPrivacy: existingConsent,
 			},
 		},
@@ -716,7 +687,7 @@ func TestCCPAConsent(t *testing.T) {
 
 	for _, test := range testCases {
 		// Build Request
-		bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt)
+		bid, err := getTestBidRequest(true, nil, test.nilRegs, &test.regs)
 		if err != nil {
 			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
 		}
@@ -729,7 +700,7 @@ func TestCCPAConsent(t *testing.T) {
 		endpoint, _ := NewAmpEndpoint(
 			fakeUUIDGenerator{},
 			mockExchange,
-			newParamsValidator(t),
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
@@ -762,15 +733,8 @@ func TestCCPAConsent(t *testing.T) {
 		if !assert.NotNil(t, result.Regs, test.description+":lastRequest.Regs") {
 			return
 		}
-		if !assert.NotNil(t, result.Regs.Ext, test.description+":lastRequest.Regs.Ext") {
-			return
-		}
-		var re openrtb_ext.ExtRegs
-		err = jsonutil.UnmarshalValid(result.Regs.Ext, &re)
-		if !assert.NoError(t, err, test.description+":deserialize") {
-			return
-		}
-		assert.Equal(t, test.expectedRegExt, re, test.description)
+
+		assert.Equal(t, test.expectedReg, result.Regs, test.description)
 		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors)
 		assert.Empty(t, response.ORTB2.Ext.Warnings)
 	}
@@ -778,7 +742,7 @@ func TestCCPAConsent(t *testing.T) {
 
 func TestConsentWarnings(t *testing.T) {
 	type inputTest struct {
-		regs              *openrtb_ext.ExtRegs
+		regs              *openrtb2.Regs
 		invalidConsentURL bool
 		expectedWarnings  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage
 	}
@@ -809,7 +773,7 @@ func TestConsentWarnings(t *testing.T) {
 			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}},
 		},
 		{
-			regs:              &openrtb_ext.ExtRegs{USPrivacy: "invalid"},
+			regs:              &openrtb2.Regs{USPrivacy: "invalid"},
 			invalidConsentURL: true,
 			expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
 				openrtb_ext.BidderReservedGeneral:  {invalidCCPAWarning, invalidConsentWarning},
@@ -817,7 +781,7 @@ func TestConsentWarnings(t *testing.T) {
 			},
 		},
 		{
-			regs:              &openrtb_ext.ExtRegs{USPrivacy: "1NYN"},
+			regs:              &openrtb2.Regs{USPrivacy: "1NYN"},
 			invalidConsentURL: false,
 			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}},
 		},
@@ -843,7 +807,7 @@ func TestConsentWarnings(t *testing.T) {
 		endpoint, _ := NewAmpEndpoint(
 			fakeUUIDGenerator{},
 			mockExchange,
-			newParamsValidator(t),
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
@@ -900,17 +864,18 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 	validConsentGDPR2 := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
 
 	testCases := []struct {
-		description     string
-		consent         string
-		consentLegacy   string
-		userExt         *openrtb_ext.ExtUser
-		expectedUserExt openrtb_ext.ExtUser
+		description   string
+		consent       string
+		consentLegacy string
+		user          *openrtb2.User
+		expectedUser  *openrtb2.User
 	}{
 		{
 			description:   "New Consent Wins",
 			consent:       validConsentGDPR1,
 			consentLegacy: validConsentGDPR2,
-			expectedUserExt: openrtb_ext.ExtUser{
+			user:          &openrtb2.User{},
+			expectedUser: &openrtb2.User{
 				Consent: validConsentGDPR1,
 			},
 		},
@@ -918,7 +883,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 			description:   "New Consent Wins - Reverse",
 			consent:       validConsentGDPR2,
 			consentLegacy: validConsentGDPR1,
-			expectedUserExt: openrtb_ext.ExtUser{
+			user:          &openrtb2.User{},
+			expectedUser: &openrtb2.User{
 				Consent: validConsentGDPR2,
 			},
 		},
@@ -926,7 +892,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 
 	for _, test := range testCases {
 		// Build Request
-		bid, err := getTestBidRequest(false, nil, true, nil)
+		bid, err := getTestBidRequest(false, test.user, true, nil)
 		if err != nil {
 			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
 		}
@@ -939,7 +905,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 		endpoint, _ := NewAmpEndpoint(
 			fakeUUIDGenerator{},
 			mockExchange,
-			newParamsValidator(t),
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 			&mockAmpStoredReqFetcher{stored},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{
@@ -975,15 +941,8 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) {
 		if !assert.NotNil(t, result.User, test.description+":lastRequest.User") {
 			return
 		}
-		if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") {
-			return
-		}
-		var ue openrtb_ext.ExtUser
-		err = jsonutil.UnmarshalValid(result.User.Ext, &ue)
-		if !assert.NoError(t, err, test.description+":deserialize") {
-			return
-		}
-		assert.Equal(t, test.expectedUserExt, ue, test.description)
+
+		assert.Equal(t, test.expectedUser, result.User, test.description)
 		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors)
 		assert.Empty(t, response.ORTB2.Ext.Warnings)
 	}
@@ -997,7 +956,7 @@ func TestAMPSiteExt(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -1027,21 +986,43 @@ func TestAMPSiteExt(t *testing.T) {
 }
 
 // TestBadRequests makes sure we return 400's on bad requests.
+// RTB26: Will need to be fixed once all validation functions are updated to rtb 2.6
 func TestAmpBadRequests(t *testing.T) {
-	dir := "sample-requests/invalid-whole"
+	dir := "sample-requests/invalid-whole/"
 	files, err := os.ReadDir(dir)
 	assert.NoError(t, err, "Failed to read folder: %s", dir)
 
-	badRequests := make(map[string]json.RawMessage, len(files))
+	mockAmpStoredReq := make(map[string]json.RawMessage, len(files))
+	badRequests := make(map[string]testCase, len(files))
+	filemap := make(map[string]string, len(files))
 	for index, file := range files {
-		badRequests[strconv.Itoa(100+index)] = readFile(t, "sample-requests/invalid-whole/"+file.Name())
+		filename := file.Name()
+		fileData := readFile(t, dir+filename)
+
+		test, err := parseTestData(fileData, filename)
+		if !assert.NoError(t, err) {
+			return
+		}
+
+		if skipAmpTest(test) {
+			continue
+		}
+
+		requestID := strconv.Itoa(100 + index)
+		test.Query = fmt.Sprintf("account=test_pub&tag_id=%s", requestID)
+
+		badRequests[requestID] = test
+		mockAmpStoredReq[requestID] = test.BidRequest
+		filemap[requestID] = filename
 	}
 
+	addAmpBadRequests(badRequests, mockAmpStoredReq)
+
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		&mockAmpExchange{},
-		newParamsValidator(t),
-		&mockAmpStoredReqFetcher{badRequests},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
+		&mockAmpStoredReqFetcher{data: mockAmpStoredReq},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
 		&metricsConfig.NilMetricsEngine{},
@@ -1053,16 +1034,73 @@ func TestAmpBadRequests(t *testing.T) {
 		hooks.EmptyPlanBuilder{},
 		nil,
 	)
-	for requestID := range badRequests {
-		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s", requestID), nil)
-		recorder := httptest.NewRecorder()
 
-		endpoint(recorder, request, nil)
+	for id, test := range badRequests {
+		t.Run(filemap[id], func(t *testing.T) {
+			request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil)
+			recorder := httptest.NewRecorder()
+
+			endpoint(recorder, request, nil)
+
+			response := recorder.Body.String()
+			assert.Equal(t, test.ExpectedReturnCode, recorder.Code, test.Description)
+			assert.Contains(t, response, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Description: %s \n", response, test.ExpectedErrorMessage, test.Description)
+		})
+	}
+}
 
-		if recorder.Code != http.StatusBadRequest {
-			t.Errorf("Expected status %d. Got %d. Input was: %s", http.StatusBadRequest, recorder.Code, fmt.Sprintf("/openrtb2/auction/amp?config=%s", requestID))
+func skipAmpTest(test testCase) bool {
+	bidRequest := openrtb2.BidRequest{}
+	if err := json.Unmarshal(test.BidRequest, &bidRequest); err == nil {
+		// request.app must not exist in AMP
+		if bidRequest.App != nil {
+			return true
 		}
+
+		// data for tag_id='%s' does not define the required imp array
+		// Invalid request: data for tag_id '%s' includes %d imp elements. Only one is allowed
+		if len(bidRequest.Imp) == 0 || len(bidRequest.Imp) > 1 {
+			return true
+		}
+
+		if bidRequest.Device != nil && strings.Contains(string(bidRequest.Device.Ext), "interstitial") {
+			return true
+		}
+	}
+
+	// request.ext.prebid.cache is initialised in AMP if it is not present in request
+	if strings.Contains(test.ExpectedErrorMessage, `Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) ||
+		strings.Contains(test.ExpectedErrorMessage, `Invalid request: ext.prebid.storedrequest.id must be a string`) {
+		return true
+	}
+
+	return false
+}
+
+func addAmpBadRequests(mapBadRequests map[string]testCase, mockAmpStoredReq map[string]json.RawMessage) {
+	mapBadRequests["201"] = testCase{
+		Description:          "missing-tag-id",
+		Query:                "account=test_pub",
+		ExpectedReturnCode:   http.StatusBadRequest,
+		ExpectedErrorMessage: "Invalid request: AMP requests require an AMP tag_id\n",
+	}
+	mockAmpStoredReq["201"] = json.RawMessage(`{}`)
+
+	mapBadRequests["202"] = testCase{
+		Description:          "request.app-present",
+		Query:                "account=test_pub&tag_id=202",
+		ExpectedReturnCode:   http.StatusBadRequest,
+		ExpectedErrorMessage: "Invalid request: request.app must not exist in AMP stored requests.\n",
+	}
+	mockAmpStoredReq["202"] = json.RawMessage(`{"imp":[{}],"app":{}}`)
+
+	mapBadRequests["203"] = testCase{
+		Description:          "request-with-2-imps",
+		Query:                "account=test_pub&tag_id=203",
+		ExpectedReturnCode:   http.StatusBadRequest,
+		ExpectedErrorMessage: "Invalid request: data for tag_id '203' includes 2 imp elements. Only one is allowed",
 	}
+	mockAmpStoredReq["203"] = json.RawMessage(`{"imp":[{},{}]}`)
 }
 
 // TestAmpDebug makes sure we get debug information back when requested
@@ -1074,7 +1112,7 @@ func TestAmpDebug(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		&mockAmpExchange{},
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -1210,7 +1248,7 @@ func TestQueryParamOverrides(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		&mockAmpExchange{},
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -1368,7 +1406,7 @@ func (s formatOverrideSpec) execute(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		&mockAmpExchange{},
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{requests},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -1478,7 +1516,7 @@ func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r *exchange.A
 	return &exchange.AuctionResponse{BidResponse: response}, nil
 }
 
-func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) {
+func getTestBidRequest(nilUser bool, user *openrtb2.User, nilRegs bool, regs *openrtb2.Regs) ([]byte, error) {
 	var width int64 = 300
 	var height int64 = 300
 	bidRequest := &openrtb2.BidRequest{
@@ -1509,37 +1547,12 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool,
 		},
 	}
 
-	var userExtData []byte
-	if userExt != nil {
-		var err error
-		userExtData, err = jsonutil.Marshal(userExt)
-		if err != nil {
-			return nil, err
-		}
-	}
-
 	if !nilUser {
-		bidRequest.User = &openrtb2.User{
-			ID:       "aUserId",
-			BuyerUID: "aBuyerID",
-			Ext:      userExtData,
-		}
-	}
-
-	var regsExtData []byte
-	if regsExt != nil {
-		var err error
-		regsExtData, err = jsonutil.Marshal(regsExt)
-		if err != nil {
-			return nil, err
-		}
+		bidRequest.User = user
 	}
 
 	if !nilRegs {
-		bidRequest.Regs = &openrtb2.Regs{
-			COPPA: 1,
-			Ext:   regsExtData,
-		}
+		bidRequest.Regs = regs
 	}
 	return jsonutil.Marshal(bidRequest)
 }
@@ -1657,6 +1670,7 @@ func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.Notificat
 func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {
 	*logger.ampObject = *ao
 }
+func (logger mockLogger) Shutdown() {}
 
 func TestBuildAmpObject(t *testing.T) {
 	testCases := []struct {
@@ -1909,7 +1923,7 @@ func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMe
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{id: "foo", err: nil},
 		exchange,
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		mockAmpFetcher,
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize, GenerateRequestID: generateRequestID},
@@ -1962,7 +1976,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{storedRequests},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -1977,7 +1991,7 @@ func TestAmpAuctionResponseHeaders(t *testing.T) {
 	)
 
 	for _, test := range testCases {
-		httpReq := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp"+test.requestURLArguments), nil)
+		httpReq := httptest.NewRequest("GET", "/openrtb2/auction/amp"+test.requestURLArguments, nil)
 		recorder := httptest.NewRecorder()
 
 		endpoint(recorder, httpReq, nil)
@@ -1998,7 +2012,7 @@ func TestRequestWithTargeting(t *testing.T) {
 	endpoint, _ := NewAmpEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		newParamsValidator(t),
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
 		&mockAmpStoredReqFetcher{stored},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -2404,3 +2418,87 @@ func TestSetSeatNonBid(t *testing.T) {
 		})
 	}
 }
+
+func TestAmpAuctionDebugWarningsOnly(t *testing.T) {
+	testCases := []struct {
+		description         string
+		requestURLArguments string
+		addRequestHeaders   func(r *http.Request)
+		expectedStatus      int
+		expectedWarnings    map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage
+	}{
+		{
+			description:         "debug_enabled_request_with_invalid_Sec-Browsing-Topics_header",
+			requestURLArguments: "?tag_id=1&debug=1",
+			addRequestHeaders: func(r *http.Request) {
+				r.Header.Add("Sec-Browsing-Topics", "foo")
+			},
+			expectedStatus: 200,
+			expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
+				"general": {
+					{
+						Code:    10012,
+						Message: "Invalid field in Sec-Browsing-Topics header: foo",
+					},
+				},
+			},
+		},
+		{
+			description:         "debug_disabled_request_with_invalid_Sec-Browsing-Topics_header",
+			requestURLArguments: "?tag_id=1",
+			addRequestHeaders: func(r *http.Request) {
+				r.Header.Add("Sec-Browsing-Topics", "foo")
+			},
+			expectedStatus:   200,
+			expectedWarnings: nil,
+		},
+	}
+
+	storedRequests := map[string]json.RawMessage{
+		"1": json.RawMessage(validRequest(t, "site.json")),
+	}
+	exchange := &nobidExchange{}
+	endpoint, _ := NewAmpEndpoint(
+		fakeUUIDGenerator{},
+		exchange,
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, newParamsValidator(t)),
+		&mockAmpStoredReqFetcher{storedRequests},
+		empty_fetcher.EmptyFetcher{},
+		&config.Configuration{
+			MaxRequestSize: maxSize,
+			AccountDefaults: config.Account{
+				Privacy: config.AccountPrivacy{
+					PrivacySandbox: config.PrivacySandbox{
+						TopicsDomain: "abc",
+					},
+				},
+			},
+		},
+		&metricsConfig.NilMetricsEngine{},
+		analyticsBuild.New(&config.Analytics{}),
+		map[string]string{},
+		[]byte{},
+		openrtb_ext.BuildBidderMap(),
+		empty_fetcher.EmptyFetcher{},
+		hooks.EmptyPlanBuilder{},
+		nil,
+	)
+
+	for _, test := range testCases {
+		httpReq := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp"+test.requestURLArguments), nil)
+		test.addRequestHeaders(httpReq)
+		recorder := httptest.NewRecorder()
+
+		endpoint(recorder, httpReq, nil)
+
+		assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode)
+
+		// Parse Response
+		var response AmpResponse
+		if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil {
+			t.Fatalf("Error unmarshalling response: %s", err.Error())
+		}
+
+		assert.Equal(t, test.expectedWarnings, response.ORTB2.Ext.Warnings)
+	}
+}
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index c7beceb1b52..2ef86f13252 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -20,15 +20,14 @@ import (
 	"github.com/julienschmidt/httprouter"
 	gpplib "github.com/prebid/go-gpp"
 	"github.com/prebid/go-gpp/constants"
-	"github.com/prebid/openrtb/v20/adcom1"
-	"github.com/prebid/openrtb/v20/native1"
-	nativeRequests "github.com/prebid/openrtb/v20/native1/request"
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/openrtb/v20/openrtb3"
 	"github.com/prebid/prebid-server/v2/bidadjustment"
 	"github.com/prebid/prebid-server/v2/hooks"
 	"github.com/prebid/prebid-server/v2/ortb"
 	"github.com/prebid/prebid-server/v2/privacy"
+	"github.com/prebid/prebid-server/v2/privacysandbox"
+	"github.com/prebid/prebid-server/v2/schain"
 	"golang.org/x/net/publicsuffix"
 	jsonpatch "gopkg.in/evanphx/json-patch.v4"
 
@@ -45,7 +44,6 @@ import (
 	"github.com/prebid/prebid-server/v2/prebid_cache_client"
 	"github.com/prebid/prebid-server/v2/privacy/ccpa"
 	"github.com/prebid/prebid-server/v2/privacy/lmt"
-	"github.com/prebid/prebid-server/v2/schain"
 	"github.com/prebid/prebid-server/v2/stored_requests"
 	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
 	"github.com/prebid/prebid-server/v2/stored_responses"
@@ -57,12 +55,16 @@ import (
 	"github.com/prebid/prebid-server/v2/version"
 )
 
-const storedRequestTimeoutMillis = 50
 const ampChannel = "amp"
 const appChannel = "app"
+const secCookieDeprecation = "Sec-Cookie-Deprecation"
+const secBrowsingTopics = "Sec-Browsing-Topics"
+const observeBrowsingTopics = "Observe-Browsing-Topics"
+const observeBrowsingTopicsValue = "?1"
 
 var (
 	dntKey      string = http.CanonicalHeaderKey("DNT")
+	secGPCKey   string = http.CanonicalHeaderKey("Sec-GPC")
 	dntDisabled int8   = 0
 	dntEnabled  int8   = 1
 	notAmp      int8   = 0
@@ -84,7 +86,7 @@ var accountIdSearchPath = [...]struct {
 func NewEndpoint(
 	uuidGenerator uuidutil.UUIDGenerator,
 	ex exchange.Exchange,
-	validator openrtb_ext.BidderParamValidator,
+	requestValidator ortb.RequestValidator,
 	requestsById stored_requests.Fetcher,
 	accounts stored_requests.AccountFetcher,
 	cfg *config.Configuration,
@@ -97,7 +99,7 @@ func NewEndpoint(
 	hookExecutionPlanBuilder hooks.ExecutionPlanBuilder,
 	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
-	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
+	if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || metricsEngine == nil {
 		return nil, errors.New("NewEndpoint requires non-nil arguments.")
 	}
 
@@ -111,7 +113,7 @@ func NewEndpoint(
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
-		validator,
+		requestValidator,
 		requestsById,
 		empty_fetcher.EmptyFetcher{},
 		accounts,
@@ -131,12 +133,10 @@ func NewEndpoint(
 		openrtb_ext.NormalizeBidderName}).Auction), nil
 }
 
-type normalizeBidderName func(name string) (openrtb_ext.BidderName, bool)
-
 type endpointDeps struct {
 	uuidGenerator             uuidutil.UUIDGenerator
 	ex                        exchange.Exchange
-	paramsValidator           openrtb_ext.BidderParamValidator
+	requestValidator          ortb.RequestValidator
 	storedReqFetcher          stored_requests.Fetcher
 	videoFetcher              stored_requests.Fetcher
 	accounts                  stored_requests.AccountFetcher
@@ -153,7 +153,7 @@ type endpointDeps struct {
 	storedRespFetcher         stored_requests.Fetcher
 	hookExecutionPlanBuilder  hooks.ExecutionPlanBuilder
 	tmaxAdjustments           *exchange.TmaxAdjustmentsPreprocessed
-	normalizeBidderName       normalizeBidderName
+	normalizeBidderName       openrtb_ext.BidderNameNormalizer
 }
 
 func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -189,6 +189,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 	}()
 
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
+	setBrowsingTopicsHeader(w, r)
 
 	req, impExtInfoMap, storedAuctionResponses, storedBidResponses, bidderImpReplaceImp, account, errL := deps.parseRequest(r, &labels, hookExecutor)
 	if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
@@ -392,6 +393,13 @@ func sendAuctionResponse(
 	return labels, ao
 }
 
+// setBrowsingTopicsHeader always set the Observe-Browsing-Topics header to a value of ?1 if the Sec-Browsing-Topics is present in request
+func setBrowsingTopicsHeader(w http.ResponseWriter, r *http.Request) {
+	if value := r.Header.Get(secBrowsingTopics); value != "" {
+		w.Header().Set(observeBrowsingTopics, observeBrowsingTopicsValue)
+	}
+}
+
 // parseRequest turns the HTTP request into an OpenRTB request. This is guaranteed to return:
 //
 //   - A context which times out appropriately, given the request.
@@ -405,6 +413,7 @@ func sendAuctionResponse(
 func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metrics.Labels, hookExecutor hookexecution.HookStageExecutor) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses stored_responses.ImpsWithBidResponses, storedBidResponses stored_responses.ImpBidderStoredResp, bidderImpReplaceImpId stored_responses.BidderImpReplaceImpID, account *config.Account, errs []error) {
 	errs = nil
 	var err error
+	var errL []error
 	var r io.ReadCloser = httpRequest.Body
 	reqContentEncoding := httputil.ContentEncoding(httpRequest.Header.Get("Content-Encoding"))
 	if reqContentEncoding != "" {
@@ -455,7 +464,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		return
 	}
 
-	timeout := parseTimeout(requestJson, time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
+	timeout := parseTimeout(requestJson, time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond)
 	ctx, cancel := context.WithTimeout(context.Background(), timeout)
 	defer cancel()
 
@@ -525,13 +534,21 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 		return
 	}
 
+	// normalize to openrtb 2.6
+	if err := openrtb_ext.ConvertUpTo26(req); err != nil {
+		errs = []error{err}
+		return
+	}
+
 	if err := mergeBidderParams(req); err != nil {
 		errs = []error{err}
 		return
 	}
 
 	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
-	deps.setFieldsImplicitly(httpRequest, req)
+	if errsL := deps.setFieldsImplicitly(httpRequest, req, account); len(errsL) > 0 {
+		errs = append(errs, errsL...)
+	}
 
 	if err := ortb.SetDefaults(req); err != nil {
 		errs = []error{err}
@@ -546,13 +563,14 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request, labels *metric
 	lmt.ModifyForIOS(req.BidRequest)
 
 	//Stored auction responses should be processed after stored requests due to possible impression modification
-	storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errs = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher)
-	if len(errs) > 0 {
+	storedAuctionResponses, storedBidResponses, bidderImpReplaceImpId, errL = stored_responses.ProcessStoredResponses(ctx, req, deps.storedRespFetcher)
+	if len(errL) > 0 {
+		errs = append(errs, errL...)
 		return nil, nil, nil, nil, nil, nil, errs
 	}
 
-	hasStoredResponses := len(storedAuctionResponses) > 0
-	errL := deps.validateRequest(req, false, hasStoredResponses, storedBidResponses, hasStoredBidRequest)
+	hasStoredAuctionResponses := len(storedAuctionResponses) > 0
+	errL = deps.validateRequest(account, httpRequest, req, false, hasStoredAuctionResponses, storedBidResponses, hasStoredBidRequest)
 	if len(errL) > 0 {
 		errs = append(errs, errL...)
 	}
@@ -657,7 +675,7 @@ func mergeBidderParamsImpExt(impExt *openrtb_ext.ImpExt, reqExtParams map[string
 	extMapModified := false
 
 	for bidder, params := range reqExtParams {
-		if !isPossibleBidder(bidder) {
+		if !openrtb_ext.IsPotentialBidder(bidder) {
 			continue
 		}
 
@@ -746,7 +764,7 @@ func mergeBidderParamsImpExtPrebid(impExt *openrtb_ext.ImpExt, reqExtParams map[
 	return nil
 }
 
-func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp, hasStoredBidRequest bool) []error {
+func (deps *endpointDeps) validateRequest(account *config.Account, httpReq *http.Request, req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredAuctionResponses bool, storedBidResp stored_responses.ImpBidderStoredResp, hasStoredBidRequest bool) []error {
 	errL := []error{}
 	if req.ID == "" {
 		return []error{errors.New("request missing required field: \"id\"")}
@@ -812,10 +830,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 		}
 	}
 
-	if err := mapSChains(req); err != nil {
-		return []error{err}
-	}
-
 	if err := validateOrFillChannel(req, isAmp); err != nil {
 		return []error{err}
 	}
@@ -875,6 +889,10 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 		return append(errL, err)
 	}
 
+	if err := validateOrFillCookieDeprecation(httpReq, req, account); err != nil {
+		errL = append(errL, err)
+	}
+
 	if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req, gpp); err != nil {
 		errL = append(errL, err)
 		if errortypes.ContainsFatalError([]error{err}) {
@@ -903,7 +921,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 		}
 		impIDs[imp.ID] = i
 
-		errs := deps.validateImp(imp, requestAliases, i, hasStoredResponses, storedBidResp)
+		errs := deps.requestValidator.ValidateImp(imp, ortb.ValidationConfig{}, i, requestAliases, hasStoredAuctionResponses, storedBidResp)
 		if len(errs) > 0 {
 			errL = append(errL, errs...)
 		}
@@ -915,32 +933,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
 	return errL
 }
 
-// mapSChains maps an schain defined in an ORTB 2.4 location (req.ext.schain) to the ORTB 2.5 location
-// (req.source.ext.schain) if no ORTB 2.5 schain (req.source.ext.schain, req.ext.prebid.schains) exists.
-// An ORTB 2.4 schain is always deleted from the 2.4 location regardless of whether an ORTB 2.5 schain exists.
-func mapSChains(req *openrtb_ext.RequestWrapper) error {
-	reqExt, err := req.GetRequestExt()
-	if err != nil {
-		return fmt.Errorf("req.ext is invalid: %v", err)
-	}
-	sourceExt, err := req.GetSourceExt()
-	if err != nil {
-		return fmt.Errorf("source.ext is invalid: %v", err)
-	}
-
-	reqExtSChain := reqExt.GetSChain()
-	reqExt.SetSChain(nil)
-
-	if reqPrebid := reqExt.GetPrebid(); reqPrebid != nil && reqPrebid.SChains != nil {
-		return nil
-	} else if sourceExt.GetSChain() != nil {
-		return nil
-	} else if reqExtSChain != nil {
-		sourceExt.SetSChain(reqExtSChain)
-	}
-	return nil
-}
-
 func validateAndFillSourceTID(req *openrtb_ext.RequestWrapper, generateRequestID bool, hasStoredBidRequest bool, isAmp bool) error {
 	if req.Source == nil {
 		req.Source = &openrtb2.Source{}
@@ -1048,558 +1040,6 @@ func (deps *endpointDeps) validateBidders(bidders []string, knownBidders map[str
 	return nil
 }
 
-func (deps *endpointDeps) validateImp(imp *openrtb_ext.ImpWrapper, aliases map[string]string, index int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error {
-	if imp.ID == "" {
-		return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
-	}
-
-	if len(imp.Metric) != 0 {
-		return []error{fmt.Errorf("request.imp[%d].metric is not yet supported by prebid-server. Support may be added in the future", index)}
-	}
-
-	if imp.Banner == nil && imp.Video == nil && imp.Audio == nil && imp.Native == nil {
-		return []error{fmt.Errorf("request.imp[%d] must contain at least one of \"banner\", \"video\", \"audio\", or \"native\"", index)}
-	}
-
-	if err := validateBanner(imp.Banner, index, isInterstitial(imp)); err != nil {
-		return []error{err}
-	}
-
-	if err := validateVideo(imp.Video, index); err != nil {
-		return []error{err}
-	}
-
-	if err := validateAudio(imp.Audio, index); err != nil {
-		return []error{err}
-	}
-
-	if err := fillAndValidateNative(imp.Native, index); err != nil {
-		return []error{err}
-	}
-
-	if err := validatePmp(imp.PMP, index); err != nil {
-		return []error{err}
-	}
-
-	errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses, storedBidResp)
-	if len(errL) != 0 {
-		return errL
-	}
-
-	return nil
-}
-
-func isInterstitial(imp *openrtb_ext.ImpWrapper) bool {
-	return imp.Instl == 1
-}
-
-func validateBanner(banner *openrtb2.Banner, impIndex int, isInterstitial bool) error {
-	if banner == nil {
-		return nil
-	}
-
-	// The following fields were previously uints in the OpenRTB library we use, but have
-	// since been changed to ints. We decided to maintain the non-negative check.
-	if banner.W != nil && *banner.W < 0 {
-		return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex)
-	}
-	if banner.H != nil && *banner.H < 0 {
-		return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex)
-	}
-
-	// The following fields are deprecated in the OpenRTB 2.5 spec but are still present
-	// in the OpenRTB library we use. Enforce they are not specified.
-	if banner.WMin != 0 {
-		return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex)
-	}
-	if banner.WMax != 0 {
-		return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmax\". Use the \"format\" array instead.", impIndex)
-	}
-	if banner.HMin != 0 {
-		return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmin\". Use the \"format\" array instead.", impIndex)
-	}
-	if banner.HMax != 0 {
-		return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"hmax\". Use the \"format\" array instead.", impIndex)
-	}
-
-	hasRootSize := banner.H != nil && banner.W != nil && *banner.H > 0 && *banner.W > 0
-	if !hasRootSize && len(banner.Format) == 0 && !isInterstitial {
-		return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex)
-	}
-
-	for i, format := range banner.Format {
-		if err := validateFormat(&format, impIndex, i); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func validateVideo(video *openrtb2.Video, impIndex int) error {
-	if video == nil {
-		return nil
-	}
-
-	if len(video.MIMEs) < 1 {
-		return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex)
-	}
-
-	// The following fields were previously uints in the OpenRTB library we use, but have
-	// since been changed to ints. We decided to maintain the non-negative check.
-	if video.W != nil && *video.W < 0 {
-		return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex)
-	}
-	if video.H != nil && *video.H < 0 {
-		return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex)
-	}
-	if video.MinBitRate < 0 {
-		return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex)
-	}
-	if video.MaxBitRate < 0 {
-		return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex)
-	}
-
-	return nil
-}
-
-func validateAudio(audio *openrtb2.Audio, impIndex int) error {
-	if audio == nil {
-		return nil
-	}
-
-	if len(audio.MIMEs) < 1 {
-		return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex)
-	}
-
-	// The following fields were previously uints in the OpenRTB library we use, but have
-	// since been changed to ints. We decided to maintain the non-negative check.
-	if audio.Sequence < 0 {
-		return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex)
-	}
-	if audio.MaxSeq < 0 {
-		return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex)
-	}
-	if audio.MinBitrate < 0 {
-		return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex)
-	}
-	if audio.MaxBitrate < 0 {
-		return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex)
-	}
-
-	return nil
-}
-
-// fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec.
-func fillAndValidateNative(n *openrtb2.Native, impIndex int) error {
-	if n == nil {
-		return nil
-	}
-
-	if len(n.Request) == 0 {
-		return fmt.Errorf("request.imp[%d].native missing required property \"request\"", impIndex)
-	}
-	var nativePayload nativeRequests.Request
-	if err := jsonutil.UnmarshalValid(json.RawMessage(n.Request), &nativePayload); err != nil {
-		return err
-	}
-
-	if err := validateNativeContextTypes(nativePayload.Context, nativePayload.ContextSubType, impIndex); err != nil {
-		return err
-	}
-	if err := validateNativePlacementType(nativePayload.PlcmtType, impIndex); err != nil {
-		return err
-	}
-	if err := fillAndValidateNativeAssets(nativePayload.Assets, impIndex); err != nil {
-		return err
-	}
-	if err := validateNativeEventTrackers(nativePayload.EventTrackers, impIndex); err != nil {
-		return err
-	}
-
-	serialized, err := jsonutil.Marshal(nativePayload)
-	if err != nil {
-		return err
-	}
-	n.Request = string(serialized)
-	return nil
-}
-
-func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error {
-	if cType == 0 {
-		// Context is only recommended, so none is a valid type.
-		return nil
-	}
-	if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) {
-		return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
-	}
-	if cSubtype < 0 {
-		return fmt.Errorf("request.imp[%d].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
-	}
-	if cSubtype == 0 {
-		return nil
-	}
-	if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated {
-		if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
-			return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
-		}
-		return nil
-	}
-	if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat {
-		if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
-			return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
-		}
-		return nil
-	}
-	if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview {
-		if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound {
-			return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype)
-		}
-		return nil
-	}
-	if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound {
-		return nil
-	}
-
-	return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex)
-}
-
-func validateNativePlacementType(pt native1.PlacementType, impIndex int) error {
-	if pt == 0 {
-		// Placement Type is only recommended, not required.
-		return nil
-	}
-	if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) {
-		return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex)
-	}
-	return nil
-}
-
-func fillAndValidateNativeAssets(assets []nativeRequests.Asset, impIndex int) error {
-	if len(assets) < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets must be an array containing at least one object", impIndex)
-	}
-
-	assetIDs := make(map[int64]struct{}, len(assets))
-
-	// If none of the asset IDs are defined by the caller, then prebid server should assign its own unique IDs. But
-	// if the caller did assign its own asset IDs, then prebid server will respect those IDs
-	assignAssetIDs := true
-	for i := 0; i < len(assets); i++ {
-		assignAssetIDs = assignAssetIDs && (assets[i].ID == 0)
-	}
-
-	for i := 0; i < len(assets); i++ {
-		if err := validateNativeAsset(assets[i], impIndex, i); err != nil {
-			return err
-		}
-
-		if assignAssetIDs {
-			assets[i].ID = int64(i)
-			continue
-		}
-
-		// Each asset should have a unique ID thats assigned by the caller
-		if _, ok := assetIDs[assets[i].ID]; ok {
-			return fmt.Errorf("request.imp[%d].native.request.assets[%d].id is already being used by another asset. Each asset ID must be unique.", impIndex, i)
-		}
-
-		assetIDs[assets[i].ID] = struct{}{}
-	}
-
-	return nil
-}
-
-func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex int) error {
-	assetErr := "request.imp[%d].native.request.assets[%d] must define exactly one of {title, img, video, data}"
-	foundType := false
-
-	if asset.Title != nil {
-		foundType = true
-		if err := validateNativeAssetTitle(asset.Title, impIndex, assetIndex); err != nil {
-			return err
-		}
-	}
-
-	if asset.Img != nil {
-		if foundType {
-			return fmt.Errorf(assetErr, impIndex, assetIndex)
-		}
-		foundType = true
-		if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil {
-			return err
-		}
-	}
-
-	if asset.Video != nil {
-		if foundType {
-			return fmt.Errorf(assetErr, impIndex, assetIndex)
-		}
-		foundType = true
-		if err := validateNativeAssetVideo(asset.Video, impIndex, assetIndex); err != nil {
-			return err
-		}
-	}
-
-	if asset.Data != nil {
-		if foundType {
-			return fmt.Errorf(assetErr, impIndex, assetIndex)
-		}
-		foundType = true
-		if err := validateNativeAssetData(asset.Data, impIndex, assetIndex); err != nil {
-			return err
-		}
-	}
-
-	if !foundType {
-		return fmt.Errorf(assetErr, impIndex, assetIndex)
-	}
-
-	return nil
-}
-
-func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impIndex int) error {
-	for i := 0; i < len(trackers); i++ {
-		if err := validateNativeEventTracker(trackers[i], impIndex, i); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error {
-	if title.Len < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex)
-	}
-	return nil
-}
-
-func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error {
-	if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) {
-		return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex)
-	}
-	if len(tracker.Methods) < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex)
-	}
-	for methodIndex, method := range tracker.Methods {
-		if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) {
-			return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex)
-		}
-	}
-
-	return nil
-}
-
-func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error {
-	if img.W < 0 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex)
-	}
-	if img.H < 0 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex)
-	}
-	if img.WMin < 0 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex)
-	}
-	if img.HMin < 0 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex)
-	}
-	return nil
-}
-
-func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error {
-	if len(video.MIMEs) < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex)
-	}
-	if video.MinDuration < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.minduration must be a positive integer", impIndex, assetIndex)
-	}
-	if video.MaxDuration < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.maxduration must be a positive integer", impIndex, assetIndex)
-	}
-	if err := validateNativeVideoProtocols(video.Protocols, impIndex, assetIndex); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error {
-	if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex)
-	}
-
-	return nil
-}
-
-func validateNativeVideoProtocols(protocols []adcom1.MediaCreativeSubtype, impIndex int, assetIndex int) error {
-	if len(protocols) < 1 {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex)
-	}
-	for i := 0; i < len(protocols); i++ {
-		if err := validateNativeVideoProtocol(protocols[i], impIndex, assetIndex, i); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func validateNativeVideoProtocol(protocol adcom1.MediaCreativeSubtype, impIndex int, assetIndex int, protocolIndex int) error {
-	if protocol < adcom1.CreativeVAST10 || protocol > adcom1.CreativeDAAST10Wrapper {
-		return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex)
-	}
-	return nil
-}
-
-func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error {
-	usesHW := format.W != 0 || format.H != 0
-	usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0
-
-	// The following fields were previously uints in the OpenRTB library we use, but have
-	// since been changed to ints. We decided to maintain the non-negative check.
-	if format.W < 0 {
-		return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex)
-	}
-	if format.H < 0 {
-		return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex)
-	}
-	if format.WRatio < 0 {
-		return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex)
-	}
-	if format.HRatio < 0 {
-		return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex)
-	}
-	if format.WMin < 0 {
-		return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex)
-	}
-
-	if usesHW && usesRatios {
-		return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex)
-	}
-	if !usesHW && !usesRatios {
-		return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} (for static size requirements) *or* {wmin, wratio, hratio} (for flexible sizes) to be non-zero.", impIndex, formatIndex)
-	}
-	if usesHW && (format.W == 0 || format.H == 0) {
-		return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"h\" and \"w\" properties.", impIndex, formatIndex)
-	}
-	if usesRatios && (format.WMin == 0 || format.WRatio == 0 || format.HRatio == 0) {
-		return fmt.Errorf("Request imp[%d].banner.format[%d] must define non-zero \"wmin\", \"wratio\", and \"hratio\" properties.", impIndex, formatIndex)
-	}
-	return nil
-}
-
-func validatePmp(pmp *openrtb2.PMP, impIndex int) error {
-	if pmp == nil {
-		return nil
-	}
-
-	for dealIndex, deal := range pmp.Deals {
-		if deal.ID == "" {
-			return fmt.Errorf("request.imp[%d].pmp.deals[%d] missing required field: \"id\"", impIndex, dealIndex)
-		}
-	}
-	return nil
-}
-
-func (deps *endpointDeps) validateImpExt(imp *openrtb_ext.ImpWrapper, aliases map[string]string, impIndex int, hasStoredResponses bool, storedBidResp stored_responses.ImpBidderStoredResp) []error {
-	if len(imp.Ext) == 0 {
-		return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)}
-	}
-
-	impExt, err := imp.GetImpExt()
-	if err != nil {
-		return []error{err}
-	}
-
-	prebid := impExt.GetOrCreatePrebid()
-	prebidModified := false
-
-	if prebid.Bidder == nil {
-		prebid.Bidder = make(map[string]json.RawMessage)
-	}
-
-	ext := impExt.GetExt()
-	extModified := false
-
-	// promote imp[].ext.BIDDER to newer imp[].ext.prebid.bidder.BIDDER location, with the later taking precedence
-	for k, v := range ext {
-		if isPossibleBidder(k) {
-			if _, exists := prebid.Bidder[k]; !exists {
-				prebid.Bidder[k] = v
-				prebidModified = true
-			}
-			delete(ext, k)
-			extModified = true
-		}
-	}
-
-	if hasStoredResponses && prebid.StoredAuctionResponse == nil {
-		return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)}
-	}
-
-	if err := deps.validateStoredBidRespAndImpExtBidders(prebid, storedBidResp, imp.ID); err != nil {
-		return []error{err}
-	}
-
-	errL := []error{}
-
-	for bidder, ext := range prebid.Bidder {
-		coreBidder, _ := openrtb_ext.NormalizeBidderName(bidder)
-		if tmp, isAlias := aliases[bidder]; isAlias {
-			coreBidder = openrtb_ext.BidderName(tmp)
-		}
-
-		if coreBidderNormalized, isValid := deps.bidderMap[coreBidder.String()]; isValid {
-			if err := deps.paramsValidator.Validate(coreBidderNormalized, ext); err != nil {
-				return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder.%s failed validation.\n%v", impIndex, bidder, err)}
-			}
-		} else {
-			if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled {
-				errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg})
-				delete(prebid.Bidder, bidder)
-				prebidModified = true
-			} else {
-				return []error{fmt.Errorf("request.imp[%d].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)}
-			}
-		}
-	}
-
-	if len(prebid.Bidder) == 0 {
-		errL = append(errL, fmt.Errorf("request.imp[%d].ext.prebid.bidder must contain at least one bidder", impIndex))
-		return errL
-	}
-
-	if prebidModified {
-		impExt.SetPrebid(prebid)
-	}
-	if extModified {
-		impExt.SetExt(ext)
-	}
-
-	return errL
-}
-
-// isPossibleBidder determines if a bidder name is a potential real bidder.
-func isPossibleBidder(bidder string) bool {
-	switch openrtb_ext.BidderName(bidder) {
-	case openrtb_ext.BidderReservedContext:
-		return false
-	case openrtb_ext.BidderReservedData:
-		return false
-	case openrtb_ext.BidderReservedGPID:
-		return false
-	case openrtb_ext.BidderReservedPrebid:
-		return false
-	case openrtb_ext.BidderReservedSKAdN:
-		return false
-	case openrtb_ext.BidderReservedTID:
-		return false
-	case openrtb_ext.BidderReservedAE:
-		return false
-	default:
-		return true
-	}
-}
-
 func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error {
 	if _, err := req.GetRequestExt(); err != nil {
 		return fmt.Errorf("request.ext is invalid: %v", err)
@@ -1692,36 +1132,28 @@ func validateRequestExt(req *openrtb_ext.RequestWrapper) []error {
 }
 
 func validateTargeting(t *openrtb_ext.ExtRequestTargeting) error {
-	if t == nil {
-		return nil
-	}
-
-	if (t.IncludeWinners == nil || !*t.IncludeWinners) && (t.IncludeBidderKeys == nil || !*t.IncludeBidderKeys) {
-		return errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support")
-	}
-
-	if t.PriceGranularity != nil {
-		if err := validatePriceGranularity(t.PriceGranularity); err != nil {
-			return err
+	if t != nil {
+		if t.PriceGranularity != nil {
+			if err := validatePriceGranularity(t.PriceGranularity); err != nil {
+				return err
+			}
 		}
-	}
-
-	if t.MediaTypePriceGranularity.Video != nil {
-		if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil {
-			return err
+		if t.MediaTypePriceGranularity.Video != nil {
+			if err := validatePriceGranularity(t.MediaTypePriceGranularity.Video); err != nil {
+				return err
+			}
 		}
-	}
-	if t.MediaTypePriceGranularity.Banner != nil {
-		if err := validatePriceGranularity(t.MediaTypePriceGranularity.Banner); err != nil {
-			return err
+		if t.MediaTypePriceGranularity.Banner != nil {
+			if err := validatePriceGranularity(t.MediaTypePriceGranularity.Banner); err != nil {
+				return err
+			}
 		}
-	}
-	if t.MediaTypePriceGranularity.Native != nil {
-		if err := validatePriceGranularity(t.MediaTypePriceGranularity.Native); err != nil {
-			return err
+		if t.MediaTypePriceGranularity.Native != nil {
+			if err := validatePriceGranularity(t.MediaTypePriceGranularity.Native); err != nil {
+				return err
+			}
 		}
 	}
-
 	return nil
 }
 
@@ -1774,8 +1206,8 @@ func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error {
 	}
 
 	if req.App.ID != "" {
-		if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found {
-			return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)}
+		if _, found := deps.cfg.BlockedAppsLookup[req.App.ID]; found {
+			return &errortypes.BlockedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)}
 		}
 	}
 
@@ -1820,6 +1252,7 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases
 	if err != nil {
 		return append(errL, fmt.Errorf("request.user.ext object is not valid: %v", err))
 	}
+
 	// Check if the buyeruids are valid
 	prebid := userExt.GetPrebid()
 	if prebid != nil {
@@ -1836,28 +1269,20 @@ func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases
 			}
 		}
 	}
+
 	// Check Universal User ID
-	eids := userExt.GetEid()
-	if eids != nil {
-		eidsValue := *eids
-		uniqueSources := make(map[string]struct{}, len(eidsValue))
-		for eidIndex, eid := range eidsValue {
-			if eid.Source == "" {
-				return append(errL, fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex))
-			}
-			if _, ok := uniqueSources[eid.Source]; ok {
-				return append(errL, errors.New("request.user.ext.eids must contain unique sources"))
-			}
-			uniqueSources[eid.Source] = struct{}{}
+	for eidIndex, eid := range req.User.EIDs {
+		if eid.Source == "" {
+			return append(errL, fmt.Errorf("request.user.eids[%d] missing required field: \"source\"", eidIndex))
+		}
 
-			if len(eid.UIDs) == 0 {
-				return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex))
-			}
+		if len(eid.UIDs) == 0 {
+			return append(errL, fmt.Errorf("request.user.eids[%d].uids must contain at least one element or be undefined", eidIndex))
+		}
 
-			for uidIndex, uid := range eid.UIDs {
-				if uid.ID == "" {
-					return append(errL, fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex))
-				}
+		for uidIndex, uid := range eid.UIDs {
+			if uid.ID == "" {
+				return append(errL, fmt.Errorf("request.user.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex))
 			}
 		}
 	}
@@ -1886,16 +1311,11 @@ func validateRegs(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) []er
 				WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
 		}
 	}
-	regsExt, err := req.GetRegExt()
-	if err != nil {
-		return append(errL, fmt.Errorf("request.regs.ext is invalid: %v", err))
-	}
 
-	gdpr := regsExt.GetGDPR()
-	if gdpr != nil && *gdpr != 0 && *gdpr != 1 {
-		return append(errL, errors.New("request.regs.ext.gdpr must be either 0 or 1"))
+	reqGDPR := req.BidRequest.Regs.GDPR
+	if reqGDPR != nil && *reqGDPR != 0 && *reqGDPR != 1 {
+		return append(errL, errors.New("request.regs.gdpr must be either 0 or 1"))
 	}
-
 	return errL
 }
 
@@ -1918,7 +1338,35 @@ func validateDevice(device *openrtb2.Device) error {
 	if device.Geo != nil && device.Geo.Accuracy < 0 {
 		return errors.New("request.device.geo.accuracy must be a positive number")
 	}
+	return nil
+}
+
+func validateOrFillCookieDeprecation(httpReq *http.Request, req *openrtb_ext.RequestWrapper, account *config.Account) error {
+	if account == nil || !account.Privacy.PrivacySandbox.CookieDeprecation.Enabled {
+		return nil
+	}
+
+	deviceExt, err := req.GetDeviceExt()
+	if err != nil {
+		return err
+	}
 
+	if deviceExt.GetCDep() != "" {
+		return nil
+	}
+
+	secCookieDeprecation := httpReq.Header.Get(secCookieDeprecation)
+	if secCookieDeprecation == "" {
+		return nil
+	}
+	if len(secCookieDeprecation) > 100 {
+		return &errortypes.Warning{
+			Message:     "request.device.ext.cdep must not exceed 100 characters",
+			WarningCode: errortypes.SecCookieDeprecationLenWarningCode,
+		}
+	}
+
+	deviceExt.SetCDep(secCookieDeprecation)
 	return nil
 }
 
@@ -2003,7 +1451,7 @@ func sanitizeRequest(r *openrtb_ext.RequestWrapper, ipValidator iputil.IPValidat
 // OpenRTB properties from the headers and other implicit info.
 //
 // This function _should not_ override any fields which were defined explicitly by the caller in the request.
-func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) {
+func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, account *config.Account) []error {
 	sanitizeRequest(r, deps.privateNetworkIPValidator)
 
 	setDeviceImplicitly(httpReq, r, deps.privateNetworkIPValidator)
@@ -2015,6 +1463,14 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, r *openrtb_
 	}
 
 	setAuctionTypeImplicitly(r)
+
+	err := setGPCImplicitly(httpReq, r)
+	if err != nil {
+		return []error{err}
+	}
+
+	errs := setSecBrowsingTopicsImplicitly(httpReq, r, account)
+	return errs
 }
 
 // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device
@@ -2022,7 +1478,6 @@ func setDeviceImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, i
 	setIPImplicitly(httpReq, r, ipValidtor)
 	setUAImplicitly(httpReq, r)
 	setDoNotTrackImplicitly(httpReq, r)
-
 }
 
 // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request,
@@ -2033,6 +1488,53 @@ func setAuctionTypeImplicitly(r *openrtb_ext.RequestWrapper) {
 	}
 }
 
+func setGPCImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) error {
+	secGPC := httpReq.Header.Get(secGPCKey)
+
+	if secGPC != "1" {
+		return nil
+	}
+
+	regExt, err := r.GetRegExt()
+	if err != nil {
+		return err
+	}
+
+	if regExt.GetGPC() != nil {
+		return nil
+	}
+
+	gpc := "1"
+	regExt.SetGPC(&gpc)
+
+	return nil
+}
+
+// setSecBrowsingTopicsImplicitly updates user.data with data from request header 'Sec-Browsing-Topics'
+func setSecBrowsingTopicsImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper, account *config.Account) []error {
+	secBrowsingTopics := httpReq.Header.Get(secBrowsingTopics)
+	if secBrowsingTopics == "" {
+		return nil
+	}
+
+	// host must configure privacy sandbox
+	if account == nil || account.Privacy.PrivacySandbox.TopicsDomain == "" {
+		return nil
+	}
+
+	topics, errs := privacysandbox.ParseTopicsFromHeader(secBrowsingTopics)
+	if len(topics) == 0 {
+		return errs
+	}
+
+	if r.User == nil {
+		r.User = &openrtb2.User{}
+	}
+
+	r.User.Data = privacysandbox.UpdateUserDataWithTopics(r.User.Data, topics, account.Privacy.PrivacySandbox.TopicsDomain)
+	return errs
+}
+
 func setSiteImplicitly(httpReq *http.Request, r *openrtb_ext.RequestWrapper) {
 	if r.Site == nil {
 		r.Site = &openrtb2.Site{}
@@ -2254,8 +1756,8 @@ func (deps *endpointDeps) processStoredRequests(requestJson []byte, impInfo []Im
 // parseImpInfo parses the request JSON and returns impression and unmarshalled imp.ext.prebid
 func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) {
 	if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array {
-		_, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) {
-			impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid")
+		_, _ = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, _ error) {
+			impExtData, _, _, _ := jsonparser.Get(imp, "ext", "prebid")
 			var impExtPrebid openrtb_ext.ExtImpPrebid
 			if impExtData != nil {
 				if err := jsonutil.Unmarshal(impExtData, &impExtPrebid); err != nil {
@@ -2362,9 +1864,9 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo
 		metricsStatus := metrics.RequestStatusBadInput
 		for _, err := range errs {
 			erVal := errortypes.ReadCode(err)
-			if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode {
+			if erVal == errortypes.BlockedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode {
 				httpStatus = http.StatusServiceUnavailable
-				metricsStatus = metrics.RequestStatusBlacklisted
+				metricsStatus = metrics.RequestStatusBlockedApp
 				break
 			} else if erVal == errortypes.MalformedAcctErrorCode {
 				httpStatus = http.StatusInternalServerError
@@ -2375,7 +1877,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo
 		w.WriteHeader(httpStatus)
 		labels.RequestStatus = metricsStatus
 		for _, err := range errs {
-			w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
+			fmt.Fprintf(w, "Invalid request: %s\n", err.Error())
 		}
 		rc = true
 	}
@@ -2503,32 +2005,3 @@ func checkIfAppRequest(request []byte) (bool, error) {
 	}
 	return false, nil
 }
-
-func (deps *endpointDeps) validateStoredBidRespAndImpExtBidders(prebid *openrtb_ext.ExtImpPrebid, storedBidResp stored_responses.ImpBidderStoredResp, impId string) error {
-	if storedBidResp == nil && len(prebid.StoredBidResponse) == 0 {
-		return nil
-	}
-
-	if storedBidResp == nil {
-		return generateStoredBidResponseValidationError(impId)
-	}
-	if bidResponses, ok := storedBidResp[impId]; ok {
-		if len(bidResponses) != len(prebid.Bidder) {
-			return generateStoredBidResponseValidationError(impId)
-		}
-
-		for bidderName := range bidResponses {
-			if _, bidderNameOk := deps.normalizeBidderName(bidderName); !bidderNameOk {
-				return fmt.Errorf(`unrecognized bidder "%v"`, bidderName)
-			}
-			if _, present := prebid.Bidder[bidderName]; !present {
-				return generateStoredBidResponseValidationError(impId)
-			}
-		}
-	}
-	return nil
-}
-
-func generateStoredBidResponseValidationError(impID string) error {
-	return fmt.Errorf("request validation failed. Stored bid responses are specified for imp %s. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse", impID)
-}
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index 835d2920af4..b4bdfbbbf21 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -149,8 +149,8 @@ func BenchmarkValidWholeExemplary(b *testing.B) {
 
 			cfg := &config.Configuration{
 				MaxRequestSize:    maxSize,
-				BlacklistedApps:   test.Config.BlacklistedApps,
-				BlacklistedAppMap: test.Config.getBlacklistedAppMap(),
+				BlockedApps:       test.Config.BlockedApps,
+				BlockedAppsLookup: test.Config.getBlockedAppLookup(),
 				AccountRequired:   test.Config.AccountRequired,
 			}
 
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index a9e16c9490b..72f5089bbf9 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -13,14 +13,14 @@ import (
 	"net/http/httptest"
 	"os"
 	"path/filepath"
+	"sort"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/buger/jsonparser"
+	jsoniter "github.com/json-iterator/go"
 	"github.com/julienschmidt/httprouter"
-	"github.com/prebid/openrtb/v20/native1"
-	nativeRequests "github.com/prebid/openrtb/v20/native1/request"
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v2/analytics"
 	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
@@ -33,6 +33,7 @@ import (
 	"github.com/prebid/prebid-server/v2/metrics"
 	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/ortb"
 	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
 	"github.com/prebid/prebid-server/v2/stored_responses"
 	"github.com/prebid/prebid-server/v2/util/iputil"
@@ -43,6 +44,11 @@ import (
 
 const jsonFileExtension string = ".json"
 
+func TestMain(m *testing.M) {
+	jsoniter.RegisterExtension(&jsonutil.RawMessageExtension{})
+	os.Exit(m.Run())
+}
+
 func TestJsonSampleRequests(t *testing.T) {
 	testSuites := []struct {
 		description          string
@@ -65,16 +71,20 @@ func TestJsonSampleRequests(t *testing.T) {
 			"invalid-native",
 		},
 		{
-			"Makes sure we handle (default) aliased bidders properly",
+			"Makes sure we handle aliased bidders properly",
 			"aliased",
 		},
+		{
+			"Makes sure we handle alternate bidder codes properly",
+			"alternate-bidder-code",
+		},
 		{
 			"Asserts we return 500s on requests referencing accounts with malformed configs.",
 			"account-malformed",
 		},
 		{
-			"Asserts we return 503s on requests with blacklisted accounts and apps.",
-			"blacklisted",
+			"Asserts we return 503s on requests with blocked apps.",
+			"blocked",
 		},
 		{
 			"Assert that requests that come with no user id nor app id return error if the `AccountRequired` field in the `config.Configuration` structure is set to true",
@@ -159,18 +169,37 @@ func runJsonBasedTest(t *testing.T, filename, desc string) {
 	// Build endpoint for testing. If no error, run test case
 	cfg := &config.Configuration{MaxRequestSize: maxSize}
 	if test.Config != nil {
-		cfg.BlacklistedApps = test.Config.BlacklistedApps
-		cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
+		cfg.BlockedApps = test.Config.BlockedApps
+		cfg.BlockedAppsLookup = test.Config.getBlockedAppLookup()
 		cfg.AccountRequired = test.Config.AccountRequired
 	}
 	cfg.MarshalAccountDefaults()
 	test.endpointType = OPENRTB_ENDPOINT
 
-	auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
+	auctionEndpointHandler, ex, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
 	if assert.NoError(t, err) {
 		assert.NotPanics(t, func() { runEndToEndTest(t, auctionEndpointHandler, test, fileData, filename) }, filename)
 	}
 
+	if test.ExpectedValidatedBidReq != nil {
+		// compare as json to ignore whitespace and ext field ordering
+		actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq)
+		if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) {
+			assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename)
+		}
+	}
+	if test.ExpectedMockBidderRequests != nil {
+		for bidder, req := range test.ExpectedMockBidderRequests {
+			a, ok := ex.adapters[openrtb_ext.BidderName(bidder)]
+			if !ok {
+				t.Fatalf("Unexpected bidder %s has an expected mock bidder request. Test file: %s", bidder, filename)
+			}
+			aa := a.(*exchange.BidderAdapter)
+			ma := aa.Bidder.(*mockAdapter)
+			assert.JSONEq(t, string(req), string(ma.requestData[0]), "Not the expected mock bidder request for bidder %s. Test file: %s", bidder, filename)
+		}
+	}
+
 	// Close servers regardless if the test case was run or not
 	for _, mockBidServer := range mockBidServers {
 		mockBidServer.Close()
@@ -443,7 +472,7 @@ func TestExplicitUserId(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		ex,
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		cfg,
@@ -501,7 +530,7 @@ func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(bidderMap, disabledBidders, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -556,32 +585,7 @@ func TestNilExchange(t *testing.T) {
 	_, err := NewEndpoint(
 		fakeUUIDGenerator{},
 		nil,
-		mockBidderParamValidator{},
-		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: maxSize},
-		&metricsConfig.NilMetricsEngine{},
-		analyticsBuild.New(&config.Analytics{}), map[string]string{},
-		[]byte{},
-		openrtb_ext.BuildBidderMap(),
-		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{},
-		nil,
-	)
-
-	if err == nil {
-		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
-	}
-}
-
-// TestNilValidator makes sure we fail when given nil for the BidderParamValidator.
-func TestNilValidator(t *testing.T) {
-	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
-	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
-	_, err := NewEndpoint(
-		fakeUUIDGenerator{},
-		&nobidExchange{},
-		nil,
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -596,7 +600,7 @@ func TestNilValidator(t *testing.T) {
 	)
 
 	if err == nil {
-		t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.")
+		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
 	}
 }
 
@@ -607,7 +611,7 @@ func TestExchangeError(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&brokenExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -734,7 +738,7 @@ func TestImplicitIPsEndToEnd(t *testing.T) {
 		endpoint, _ := NewEndpoint(
 			fakeUUIDGenerator{},
 			exchange,
-			mockBidderParamValidator{},
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 			&mockStoredReqFetcher{},
 			empty_fetcher.EmptyFetcher{},
 			cfg,
@@ -934,7 +938,7 @@ func TestImplicitDNTEndToEnd(t *testing.T) {
 		endpoint, _ := NewEndpoint(
 			fakeUUIDGenerator{},
 			exchange,
-			mockBidderParamValidator{},
+			ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 			&mockStoredReqFetcher{},
 			empty_fetcher.EmptyFetcher{},
 			&config.Configuration{MaxRequestSize: maxSize},
@@ -1170,7 +1174,7 @@ func TestStoredRequests(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1620,7 +1624,7 @@ func TestValidateRequest(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -1644,6 +1648,8 @@ func TestValidateRequest(t *testing.T) {
 		description           string
 		givenIsAmp            bool
 		givenRequestWrapper   *openrtb_ext.RequestWrapper
+		givenHttpRequest      *http.Request
+		givenAccount          *config.Account
 		expectedErrorList     []error
 		expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel
 	}{
@@ -1864,7 +1870,7 @@ func TestValidateRequest(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp, false, nil, false)
+		errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, test.givenIsAmp, false, nil, false)
 		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
 
 		if len(errorList) == 0 {
@@ -1933,9 +1939,13 @@ func TestValidateRequestExt(t *testing.T) {
 			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{},"vastxml":{}}}}`),
 		},
 		{
-			description:     "prebid targeting", // test integration with validateTargeting
-			givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{}}}`),
-			expectedErrors:  []string{"ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"},
+			description:     "prebid price granularity invalid",
+			givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":-1,"ranges":[{"min":0,"max":20,"increment":0.1}]}}}}`),
+			expectedErrors:  []string{"Price granularity error: precision must be non-negative"},
+		},
+		{
+			description:     "prebid native media type price granualrity valid",
+			givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"mediatypepricegranularity":{"native":{"precision":3,"ranges":[{"max":20,"increment":4.5}]}}}}}`),
 		},
 		{
 			description:     "valid multibid",
@@ -1976,75 +1986,9 @@ func TestValidateTargeting(t *testing.T) {
 			givenTargeting: nil,
 			expectedError:  nil,
 		},
-		{
-			name:           "empty",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{},
-			expectedError:  errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"),
-		},
-		{
-			name: "includewinners nil, includebidderkeys false",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeBidderKeys: ptrutil.ToPtr(false),
-			},
-			expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"),
-		},
-		{
-			name: "includewinners nil, includebidderkeys true",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeBidderKeys: ptrutil.ToPtr(true),
-			},
-			expectedError: nil,
-		},
-		{
-			name: "includewinners false, includebidderkeys nil",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(false),
-			},
-			expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"),
-		},
-		{
-			name: "includewinners true, includebidderkeys nil",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
-			},
-			expectedError: nil,
-		},
-		{
-			name: "all false",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners:    ptrutil.ToPtr(false),
-				IncludeBidderKeys: ptrutil.ToPtr(false),
-			},
-			expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"),
-		},
-		{
-			name: "includewinners false, includebidderkeys true",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners:    ptrutil.ToPtr(false),
-				IncludeBidderKeys: ptrutil.ToPtr(true),
-			},
-			expectedError: nil,
-		},
-		{
-			name: "includewinners false, includebidderkeys true",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners:    ptrutil.ToPtr(true),
-				IncludeBidderKeys: ptrutil.ToPtr(false),
-			},
-			expectedError: nil,
-		},
-		{
-			name: "includewinners true, includebidderkeys true",
-			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners:    ptrutil.ToPtr(true),
-				IncludeBidderKeys: ptrutil.ToPtr(true),
-			},
-			expectedError: nil,
-		},
 		{
 			name: "price granularity ranges out of order",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				PriceGranularity: &openrtb_ext.PriceGranularity{
 					Precision: ptrutil.ToPtr(2),
 					Ranges: []openrtb_ext.GranularityRange{
@@ -2058,7 +2002,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity video correct",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Video: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2073,7 +2016,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity banner correct",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Banner: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2088,7 +2030,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity native correct",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Native: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2103,7 +2044,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity video and banner correct",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Banner: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2124,7 +2064,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity video incorrect",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Video: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2139,7 +2078,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity banner incorrect",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Banner: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2154,7 +2092,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity native incorrect",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Native: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2169,7 +2106,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity video correct and banner incorrect",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Banner: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2190,7 +2126,6 @@ func TestValidateTargeting(t *testing.T) {
 		{
 			name: "media type price granularity native incorrect and banner correct",
 			givenTargeting: &openrtb_ext.ExtRequestTargeting{
-				IncludeWinners: ptrutil.ToPtr(true),
 				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
 					Native: &openrtb_ext.PriceGranularity{
 						Precision: ptrutil.ToPtr(2),
@@ -2400,7 +2335,7 @@ func TestSetIntegrationType(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2467,7 +2402,7 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{id: "foo", err: nil},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2572,7 +2507,7 @@ func TestOversizedRequest(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2612,7 +2547,7 @@ func TestRequestSizeEdgeCase(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -2651,7 +2586,7 @@ func TestNoEncoding(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&mockExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -2736,7 +2671,7 @@ func TestContentType(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		&mockExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -2758,241 +2693,6 @@ func TestContentType(t *testing.T) {
 	}
 }
 
-func TestValidateImpExt(t *testing.T) {
-	type testCase struct {
-		description    string
-		impExt         json.RawMessage
-		expectedImpExt string
-		expectedErrs   []error
-	}
-	testGroups := []struct {
-		description string
-		testCases   []testCase
-	}{
-		{
-			"Empty",
-			[]testCase{
-				{
-					description:    "Empty",
-					impExt:         nil,
-					expectedImpExt: "",
-					expectedErrs:   []error{errors.New("request.imp[0].ext is required")},
-				},
-			},
-		},
-		{
-			"Unknown bidder tests",
-			[]testCase{
-				{
-					description:    "Unknown Bidder only",
-					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555}}`),
-					expectedImpExt: `{"unknownbidder":{"placement_id":555}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Unknown Prebid Ext Bidder only",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Unknown Prebid Ext Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Unknown Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Unknown Bidder + Disabled Bidder",
-					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`),
-					expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Unknown Bidder + Disabled Prebid Ext Bidder",
-					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`),
-					expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-			},
-		},
-		{
-			"Disabled bidder tests",
-			[]testCase{
-				{
-					description:    "Disabled Bidder",
-					impExt:         json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`),
-					expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`,
-					expectedErrs: []error{
-						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
-						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
-					},
-					// if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error
-				},
-				{
-					description:    "Disabled Prebid Ext Bidder",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`,
-					expectedErrs: []error{
-						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
-						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
-					},
-				},
-				{
-					description:    "Disabled Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs: []error{
-						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
-						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
-					},
-				},
-				{
-					description:    "Disabled Prebid Ext Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs: []error{
-						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
-						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
-					},
-				},
-			},
-		},
-		{
-			"First Party only",
-			[]testCase{
-				{
-					description:    "First Party Data Context",
-					impExt:         json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs: []error{
-						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
-					},
-				},
-			},
-		},
-		{
-			"Valid bidder tests",
-			[]testCase{
-				{
-					description:    "Valid bidder root ext",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
-					expectedErrs:   []error{},
-				},
-				{
-					description:    "Valid bidder in prebid field",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
-					expectedErrs:   []error{},
-				},
-				{
-					description:    "Valid Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{},
-				},
-				{
-					description:    "Valid Prebid Ext Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{},
-				},
-				{
-					description:    "Valid Bidder + Unknown Bidder",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`),
-					expectedImpExt: `{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Valid Bidder + Disabled Bidder",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
-					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
-				},
-				{
-					description:    "Valid Bidder + Disabled Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
-				},
-				{
-					description:    "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-				{
-					description:    "Valid Prebid Ext Bidder + Disabled Bidder Ext",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`,
-					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
-				},
-				{
-					description:    "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
-				},
-				{
-					description:    "Valid Prebid Ext Bidder + Disabled Ext Bidder + Unknown Ext + First Party Data Context",
-					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`),
-					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
-					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
-				},
-			},
-		},
-	}
-
-	deps := &endpointDeps{
-		fakeUUIDGenerator{},
-		&nobidExchange{},
-		mockBidderParamValidator{},
-		&mockStoredReqFetcher{},
-		empty_fetcher.EmptyFetcher{},
-		empty_fetcher.EmptyFetcher{},
-		&config.Configuration{MaxRequestSize: int64(8096)},
-		&metricsConfig.NilMetricsEngine{},
-		analyticsBuild.New(&config.Analytics{}),
-		map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."},
-		false,
-		[]byte{},
-		openrtb_ext.BuildBidderMap(),
-		nil,
-		nil,
-		hardcodedResponseIPValidator{response: true},
-		empty_fetcher.EmptyFetcher{},
-		hooks.EmptyPlanBuilder{},
-		nil,
-		openrtb_ext.NormalizeBidderName,
-	}
-
-	for _, group := range testGroups {
-		for _, test := range group.testCases {
-			t.Run(test.description, func(t *testing.T) {
-				imp := &openrtb2.Imp{Ext: test.impExt}
-				impWrapper := &openrtb_ext.ImpWrapper{Imp: imp}
-
-				errs := deps.validateImpExt(impWrapper, nil, 0, false, nil)
-
-				assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp")
-
-				if len(test.expectedImpExt) > 0 {
-					assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description)
-				} else {
-					assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description)
-				}
-				assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description)
-			})
-		}
-	}
-}
-
 func validRequest(t *testing.T, filename string) string {
 	requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
 	if err != nil {
@@ -3008,7 +2708,7 @@ func TestCurrencyTrunc(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3047,7 +2747,7 @@ func TestCurrencyTrunc(t *testing.T) {
 		Cur: []string{"USD", "EUR"},
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 
 	expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"}
 	assert.ElementsMatch(t, errL, []error{&expectedError})
@@ -3057,7 +2757,7 @@ func TestCCPAInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3094,11 +2794,11 @@ func TestCCPAInvalid(t *testing.T) {
 			ID: "anySiteID",
 		},
 		Regs: &openrtb2.Regs{
-			Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`),
+			USPrivacy: "invalid by length",
 		},
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 
 	expectedWarning := errortypes.Warning{
 		Message:     "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
@@ -3110,7 +2810,7 @@ func TestNoSaleInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3152,7 +2852,7 @@ func TestNoSaleInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 
 	expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided")
 	assert.ElementsMatch(t, errL, []error{expectedError})
@@ -3166,7 +2866,7 @@ func TestValidateSourceTID(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3204,7 +2904,7 @@ func TestValidateSourceTID(t *testing.T) {
 		},
 	}
 
-	deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 	assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID")
 }
 
@@ -3212,7 +2912,7 @@ func TestSChainInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3251,140 +2951,12 @@ func TestSChainInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 
 	expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
 	assert.ElementsMatch(t, errL, []error{expectedError})
 }
 
-func TestMapSChains(t *testing.T) {
-	const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
-	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
-
-	seller1SChainUnpacked := openrtb2.SupplyChain{
-		Complete: 1,
-		Nodes: []openrtb2.SupplyChainNode{{
-			ASI: "directseller1.com",
-			SID: "00001",
-			RID: "BidRequest1",
-			HP:  openrtb2.Int8Ptr(1),
-		}},
-		Ver: "1.0",
-	}
-
-	tests := []struct {
-		description         string
-		bidRequest          openrtb2.BidRequest
-		wantReqExtSChain    *openrtb2.SupplyChain
-		wantSourceExtSChain *openrtb2.SupplyChain
-		wantError           bool
-	}{
-		{
-			description: "invalid req.ext",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{}`),
-				},
-			},
-			wantError: true,
-		},
-		{
-			description: "invalid source.ext",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{"schain":invalid}}`),
-				},
-			},
-			wantError: true,
-		},
-		{
-			description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{}`),
-				},
-			},
-			wantReqExtSChain:    nil,
-			wantSourceExtSChain: nil,
-		},
-		{
-			description: "req.ext.prebid.schains is not nil",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{}`),
-				},
-			},
-			wantReqExtSChain:    nil,
-			wantSourceExtSChain: nil,
-		},
-		{
-			description: "req.source.ext is not nil",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller1SChain + `}`),
-				},
-			},
-			wantReqExtSChain:    nil,
-			wantSourceExtSChain: &seller1SChainUnpacked,
-		},
-		{
-			description: "req.ext.schain is not nil",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{` + seller1SChain + `}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{}`),
-				},
-			},
-			wantReqExtSChain:    nil,
-			wantSourceExtSChain: &seller1SChainUnpacked,
-		},
-		{
-			description: "req.source.ext.schain and req.ext.schain are not nil",
-			bidRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{` + seller2SChain + `}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller1SChain + `}`),
-				},
-			},
-			wantReqExtSChain:    nil,
-			wantSourceExtSChain: &seller1SChainUnpacked,
-		},
-	}
-
-	for _, test := range tests {
-		reqWrapper := openrtb_ext.RequestWrapper{
-			BidRequest: &test.bidRequest,
-		}
-
-		err := mapSChains(&reqWrapper)
-
-		if test.wantError {
-			assert.NotNil(t, err, test.description)
-		} else {
-			assert.Nil(t, err, test.description)
-
-			reqExt, err := reqWrapper.GetRequestExt()
-			if err != nil {
-				assert.Fail(t, "Error getting request ext from wrapper", test.description)
-			}
-			reqExtSChain := reqExt.GetSChain()
-			assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description)
-
-			sourceExt, err := reqWrapper.GetSourceExt()
-			if err != nil {
-				assert.Fail(t, "Error getting source ext from wrapper", test.description)
-			}
-			sourceExtSChain := sourceExt.GetSChain()
-			assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description)
-		}
-	}
-}
-
 func TestSearchAccountID(t *testing.T) {
 	// Correctness for lookup within Publisher object left to TestGetAccountID
 	// This however tests the expected lookup paths in outer site, app and dooh
@@ -3781,7 +3353,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -3820,7 +3392,7 @@ func TestEidPermissionsInvalid(t *testing.T) {
 		Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`),
 	}
 
-	errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
+	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
 
 	expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`)
 	assert.ElementsMatch(t, errL, []error{expectedError})
@@ -3831,51 +3403,51 @@ func TestValidateEidPermissions(t *testing.T) {
 	knownAliases := map[string]string{"b": "b"}
 
 	testCases := []struct {
-		description   string
+		name          string
 		request       *openrtb_ext.ExtRequest
 		expectedError error
 	}{
 		{
-			description:   "Valid - Empty ext",
+			name:          "valid-empty-ext",
 			request:       &openrtb_ext.ExtRequest{},
 			expectedError: nil,
 		},
 		{
-			description:   "Valid - Nil ext.prebid.data",
+			name:          "valid-nil-ext.prebid.data",
 			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{}},
 			expectedError: nil,
 		},
 		{
-			description:   "Valid - Empty ext.prebid.data",
+			name:          "valid-empty-ext.prebid.data",
 			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{}}},
 			expectedError: nil,
 		},
 		{
-			description:   "Valid - Nil ext.prebid.data.eidpermissions",
+			name:          "valid-nil-ext.prebid.data.eidpermissions",
 			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: nil}}},
 			expectedError: nil,
 		},
 		{
-			description:   "Valid - None",
+			name:          "valid-none",
 			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}}}},
 			expectedError: nil,
 		},
 		{
-			description: "Valid - One",
+			name: "valid-one",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 			}}}},
 			expectedError: nil,
 		},
 		{
-			description: "Valid - One - Case Insensitive",
+			name: "valid-one-case-insensitive",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"A"}},
 			}}}},
 			expectedError: nil,
 		},
 		{
-			description: "Valid - Many",
+			name: "valid-many",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Source: "sourceB", Bidders: []string{"a"}},
@@ -3883,7 +3455,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: nil,
 		},
 		{
-			description: "Invalid - Missing Source",
+			name: "invalid-missing-source",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Bidders: []string{"a"}},
@@ -3891,7 +3463,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing required field: "source"`),
 		},
 		{
-			description: "Invalid - Duplicate Source",
+			name: "invalid-duplicate-source",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Source: "sourceA", Bidders: []string{"a"}},
@@ -3899,7 +3471,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] duplicate entry with field: "source"`),
 		},
 		{
-			description: "Invalid - Missing Bidders - Nil",
+			name: "invalid-missing-bidders-nil",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Source: "sourceB"},
@@ -3907,7 +3479,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
 		},
 		{
-			description: "Invalid - Missing Bidders - Empty",
+			name: "invalid-missing-bidders-empty",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Source: "sourceB", Bidders: []string{}},
@@ -3915,7 +3487,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
 		},
 		{
-			description: "Invalid - Invalid Bidders",
+			name: "invalid-invalid-bidders",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"a"}},
 				{Source: "sourceB", Bidders: []string{"z"}},
@@ -3923,7 +3495,7 @@ func TestValidateEidPermissions(t *testing.T) {
 			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`),
 		},
 		{
-			description: "Valid - Alias Case Sensitive",
+			name: "invalid-alias-case-sensitive",
 			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "sourceA", Bidders: []string{"B"}},
 			}}}},
@@ -3933,8 +3505,10 @@ func TestValidateEidPermissions(t *testing.T) {
 
 	endpoint := &endpointDeps{bidderMap: knownBidders, normalizeBidderName: fakeNormalizeBidderName}
 	for _, test := range testCases {
-		result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases)
-		assert.Equal(t, test.expectedError, result, test.description)
+		t.Run(test.name, func(t *testing.T) {
+			result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases)
+			assert.Equal(t, test.expectedError, result)
+		})
 	}
 }
 
@@ -4059,7 +3633,7 @@ func TestIOS14EndToEnd(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -4121,7 +3695,7 @@ func TestAuctionWarnings(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&warningsCheckExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -4168,7 +3742,7 @@ func TestParseRequestParseImpInfoError(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&warningsCheckExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -4249,7 +3823,7 @@ func TestParseGzipedRequest(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&warningsCheckExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -4301,448 +3875,56 @@ func TestParseGzipedRequest(t *testing.T) {
 	}
 }
 
-func TestValidateNativeContextTypes(t *testing.T) {
-	impIndex := 4
-
+func TestAuctionResponseHeaders(t *testing.T) {
 	testCases := []struct {
-		description      string
-		givenContextType native1.ContextType
-		givenSubType     native1.ContextSubType
-		expectedError    string
+		description     string
+		httpRequest     *http.Request
+		expectedStatus  int
+		expectedHeaders func(http.Header)
 	}{
 		{
-			description:      "No Types Specified",
-			givenContextType: 0,
-			givenSubType:     0,
-			expectedError:    "",
-		},
-		{
-			description:      "All Types Exchange Specific",
-			givenContextType: 500,
-			givenSubType:     500,
-			expectedError:    "",
-		},
-		{
-			description:      "Context Type Known Value - Sub Type Unspecified",
-			givenContextType: 1,
-			givenSubType:     0,
-			expectedError:    "",
-		},
-		{
-			description:      "Context Type Negative",
-			givenContextType: -1,
-			givenSubType:     0,
-			expectedError:    "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Context Type Just Above Range",
-			givenContextType: 4, // Range is currently 1-3
-			givenSubType:     0,
-			expectedError:    "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Sub Type Negative",
-			givenContextType: 1,
-			givenSubType:     -1,
-			expectedError:    "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Content - Sub Type Just Below Range",
-			givenContextType: 1, // Content constant
-			givenSubType:     9, // Content range is currently 10-15
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Content - Sub Type In Range",
-			givenContextType: 1,  // Content constant
-			givenSubType:     10, // Content range is currently 10-15
-			expectedError:    "",
-		},
-		{
-			description:      "Content - Sub Type In Range - Context Type Exchange Specific Boundary",
-			givenContextType: 500,
-			givenSubType:     10, // Content range is currently 10-15
-			expectedError:    "",
-		},
-		{
-			description:      "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
-			givenContextType: 501,
-			givenSubType:     10, // Content range is currently 10-15
-			expectedError:    "",
-		},
-		{
-			description:      "Content - Sub Type Just Above Range",
-			givenContextType: 1,  // Content constant
-			givenSubType:     16, // Content range is currently 10-15
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Content - Sub Type Exchange Specific Boundary",
-			givenContextType: 1, // Content constant
-			givenSubType:     500,
-			expectedError:    "",
-		},
-		{
-			description:      "Content - Sub Type Exchange Specific Boundary + 1",
-			givenContextType: 1, // Content constant
-			givenSubType:     501,
-			expectedError:    "",
-		},
-		{
-			description:      "Content - Invalid Context Type",
-			givenContextType: 2,  // Not content constant
-			givenSubType:     10, // Content range is currently 10-15
-			expectedError:    "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Social - Sub Type Just Below Range",
-			givenContextType: 2,  // Social constant
-			givenSubType:     19, // Social range is currently 20-22
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Social - Sub Type In Range",
-			givenContextType: 2,  // Social constant
-			givenSubType:     20, // Social range is currently 20-22
-			expectedError:    "",
-		},
-		{
-			description:      "Social - Sub Type In Range - Context Type Exchange Specific Boundary",
-			givenContextType: 500,
-			givenSubType:     20, // Social range is currently 20-22
-			expectedError:    "",
-		},
-		{
-			description:      "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
-			givenContextType: 501,
-			givenSubType:     20, // Social range is currently 20-22
-			expectedError:    "",
-		},
-		{
-			description:      "Social - Sub Type Just Above Range",
-			givenContextType: 2,  // Social constant
-			givenSubType:     23, // Social range is currently 20-22
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
+			description:    "Success Response",
+			httpRequest:    httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))),
+			expectedStatus: 200,
+			expectedHeaders: func(h http.Header) {
+				h.Set("X-Prebid", "pbs-go/unknown")
+				h.Set("Content-Type", "application/json")
+			},
 		},
 		{
-			description:      "Social - Sub Type Exchange Specific Boundary",
-			givenContextType: 2, // Social constant
-			givenSubType:     500,
-			expectedError:    "",
+			description:    "Failure Response",
+			httpRequest:    httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}")),
+			expectedStatus: 400,
+			expectedHeaders: func(h http.Header) {
+				h.Set("X-Prebid", "pbs-go/unknown")
+			},
 		},
 		{
-			description:      "Social - Sub Type Exchange Specific Boundary + 1",
-			givenContextType: 2, // Social constant
-			givenSubType:     501,
-			expectedError:    "",
+			description: "Success Response with Chrome BrowsingTopicsHeader",
+			httpRequest: func() *http.Request {
+				httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
+				httpReq.Header.Add(secBrowsingTopics, "sample-value")
+				return httpReq
+			}(),
+			expectedStatus: 200,
+			expectedHeaders: func(h http.Header) {
+				h.Set("X-Prebid", "pbs-go/unknown")
+				h.Set("Content-Type", "application/json")
+				h.Set("Observe-Browsing-Topics", "?1")
+			},
 		},
 		{
-			description:      "Social - Invalid Context Type",
-			givenContextType: 3,  // Not social constant
-			givenSubType:     20, // Social range is currently 20-22
-			expectedError:    "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Product - Sub Type Just Below Range",
-			givenContextType: 3,  // Product constant
-			givenSubType:     29, // Product range is currently 30-32
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Product - Sub Type In Range",
-			givenContextType: 3,  // Product constant
-			givenSubType:     30, // Product range is currently 30-32
-			expectedError:    "",
-		},
-		{
-			description:      "Product - Sub Type In Range - Context Type Exchange Specific Boundary",
-			givenContextType: 500,
-			givenSubType:     30, // Product range is currently 30-32
-			expectedError:    "",
-		},
-		{
-			description:      "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
-			givenContextType: 501,
-			givenSubType:     30, // Product range is currently 30-32
-			expectedError:    "",
-		},
-		{
-			description:      "Product - Sub Type Just Above Range",
-			givenContextType: 3,  // Product constant
-			givenSubType:     33, // Product range is currently 30-32
-			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-		{
-			description:      "Product - Sub Type Exchange Specific Boundary",
-			givenContextType: 3, // Product constant
-			givenSubType:     500,
-			expectedError:    "",
-		},
-		{
-			description:      "Product - Sub Type Exchange Specific Boundary + 1",
-			givenContextType: 3, // Product constant
-			givenSubType:     501,
-			expectedError:    "",
-		},
-		{
-			description:      "Product - Invalid Context Type",
-			givenContextType: 1,  // Not product constant
-			givenSubType:     30, // Product range is currently 30-32
-			expectedError:    "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
-		},
-	}
-
-	for _, test := range testCases {
-		err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex)
-		if test.expectedError == "" {
-			assert.NoError(t, err, test.description)
-		} else {
-			assert.EqualError(t, err, test.expectedError, test.description)
-		}
-	}
-}
-
-func TestValidateNativePlacementType(t *testing.T) {
-	impIndex := 4
-
-	testCases := []struct {
-		description        string
-		givenPlacementType native1.PlacementType
-		expectedError      string
-	}{
-		{
-			description:        "Not Specified",
-			givenPlacementType: 0,
-			expectedError:      "",
-		},
-		{
-			description:        "Known Value",
-			givenPlacementType: 1, // Range is currently 1-4
-			expectedError:      "",
-		},
-		{
-			description:        "Exchange Specific - Boundary",
-			givenPlacementType: 500,
-			expectedError:      "",
-		},
-		{
-			description:        "Exchange Specific - Boundary + 1",
-			givenPlacementType: 501,
-			expectedError:      "",
-		},
-		{
-			description:        "Negative",
-			givenPlacementType: -1,
-			expectedError:      "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
-		},
-		{
-			description:        "Just Above Range",
-			givenPlacementType: 5, // Range is currently 1-4
-			expectedError:      "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
-		},
-	}
-
-	for _, test := range testCases {
-		err := validateNativePlacementType(test.givenPlacementType, impIndex)
-		if test.expectedError == "" {
-			assert.NoError(t, err, test.description)
-		} else {
-			assert.EqualError(t, err, test.expectedError, test.description)
-		}
-	}
-}
-
-func TestValidateNativeEventTracker(t *testing.T) {
-	impIndex := 4
-	eventIndex := 8
-
-	testCases := []struct {
-		description   string
-		givenEvent    nativeRequests.EventTracker
-		expectedError string
-	}{
-		{
-			description: "Valid",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{1},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Event - Exchange Specific - Boundary",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   500,
-				Methods: []native1.EventTrackingMethod{1},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Event - Exchange Specific - Boundary + 1",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   501,
-				Methods: []native1.EventTrackingMethod{1},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Event - Negative",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   -1,
-				Methods: []native1.EventTrackingMethod{1},
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-		{
-			description: "Event - Just Above Range",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   5, // Range is currently 1-4
-				Methods: []native1.EventTrackingMethod{1},
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-		{
-			description: "Methods - Many Valid",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{1, 2},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Methods - Empty",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{},
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-		{
-			description: "Methods - Exchange Specific - Boundary",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{500},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Methods - Exchange Specific - Boundary + 1",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{501},
-			},
-			expectedError: "",
-		},
-		{
-			description: "Methods - Negative",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{-1},
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-		{
-			description: "Methods - Just Above Range",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-		{
-			description: "Methods - Mixed Valid + Invalid",
-			givenEvent: nativeRequests.EventTracker{
-				Event:   1,
-				Methods: []native1.EventTrackingMethod{1, -1},
-			},
-			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
-		},
-	}
-
-	for _, test := range testCases {
-		err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex)
-		if test.expectedError == "" {
-			assert.NoError(t, err, test.description)
-		} else {
-			assert.EqualError(t, err, test.expectedError, test.description)
-		}
-	}
-}
-
-func TestValidateNativeAssetData(t *testing.T) {
-	impIndex := 4
-	assetIndex := 8
-
-	testCases := []struct {
-		description   string
-		givenData     nativeRequests.Data
-		expectedError string
-	}{
-		{
-			description:   "Valid",
-			givenData:     nativeRequests.Data{Type: 1},
-			expectedError: "",
-		},
-		{
-			description:   "Exchange Specific - Boundary",
-			givenData:     nativeRequests.Data{Type: 500},
-			expectedError: "",
-		},
-		{
-			description:   "Exchange Specific - Boundary + 1",
-			givenData:     nativeRequests.Data{Type: 501},
-			expectedError: "",
-		},
-		{
-			description:   "Not Specified",
-			givenData:     nativeRequests.Data{},
-			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
-		},
-		{
-			description:   "Negative",
-			givenData:     nativeRequests.Data{Type: -1},
-			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
-		},
-		{
-			description:   "Just Above Range",
-			givenData:     nativeRequests.Data{Type: 13}, // Range is currently 1-12
-			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
-		},
-	}
-
-	for _, test := range testCases {
-		err := validateNativeAssetData(&test.givenData, impIndex, assetIndex)
-		if test.expectedError == "" {
-			assert.NoError(t, err, test.description)
-		} else {
-			assert.EqualError(t, err, test.expectedError, test.description)
-		}
-	}
-}
-
-func TestAuctionResponseHeaders(t *testing.T) {
-	testCases := []struct {
-		description     string
-		requestBody     string
-		expectedStatus  int
-		expectedHeaders func(http.Header)
-	}{
-		{
-			description:    "Success Response",
-			requestBody:    validRequest(t, "site.json"),
-			expectedStatus: 200,
-			expectedHeaders: func(h http.Header) {
-				h.Set("X-Prebid", "pbs-go/unknown")
-				h.Set("Content-Type", "application/json")
-			},
-		},
-		{
-			description:    "Failure Response",
-			requestBody:    "{}",
-			expectedStatus: 400,
-			expectedHeaders: func(h http.Header) {
-				h.Set("X-Prebid", "pbs-go/unknown")
-			},
+			description: "Failure Response with Chrome BrowsingTopicsHeader",
+			httpRequest: func() *http.Request {
+				httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}"))
+				httpReq.Header.Add(secBrowsingTopics, "sample-value")
+				return httpReq
+			}(),
+			expectedStatus: 400,
+			expectedHeaders: func(h http.Header) {
+				h.Set("X-Prebid", "pbs-go/unknown")
+				h.Set("Observe-Browsing-Topics", "?1")
+			},
 		},
 	}
 
@@ -4750,7 +3932,7 @@ func TestAuctionResponseHeaders(t *testing.T) {
 	endpoint, _ := NewEndpoint(
 		fakeUUIDGenerator{},
 		exchange,
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		&config.Configuration{MaxRequestSize: maxSize},
@@ -4765,10 +3947,9 @@ func TestAuctionResponseHeaders(t *testing.T) {
 	)
 
 	for _, test := range testCases {
-		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody))
 		recorder := httptest.NewRecorder()
 
-		endpoint(recorder, httpReq, nil)
+		endpoint(recorder, test.httpRequest, nil)
 
 		expectedHeaders := http.Header{}
 		test.expectedHeaders(expectedHeaders)
@@ -4782,38 +3963,6 @@ func TestAuctionResponseHeaders(t *testing.T) {
 
 // Test stored request data
 
-func TestValidateBanner(t *testing.T) {
-	impIndex := 0
-
-	testCases := []struct {
-		description    string
-		banner         *openrtb2.Banner
-		impIndex       int
-		isInterstitial bool
-		expectedError  error
-	}{
-		{
-			description:    "isInterstitial Equals False (not set to 1)",
-			banner:         &openrtb2.Banner{W: nil, H: nil, Format: nil},
-			impIndex:       impIndex,
-			isInterstitial: false,
-			expectedError:  errors.New("request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements."),
-		},
-		{
-			description:    "isInterstitial Equals True (is set to 1)",
-			banner:         &openrtb2.Banner{W: nil, H: nil, Format: nil},
-			impIndex:       impIndex,
-			isInterstitial: true,
-			expectedError:  nil,
-		},
-	}
-
-	for _, test := range testCases {
-		result := validateBanner(test.banner, test.impIndex, test.isInterstitial)
-		assert.Equal(t, test.expectedError, result, test.description)
-	}
-}
-
 func TestParseRequestMergeBidderParams(t *testing.T) {
 	tests := []struct {
 		name               string
@@ -4850,7 +3999,7 @@ func TestParseRequestMergeBidderParams(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				mockBidderParamValidator{},
+				ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -4954,7 +4103,7 @@ func TestParseRequestStoredResponses(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				mockBidderParamValidator{},
+				ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -5067,7 +4216,7 @@ func TestParseRequestStoredBidResponses(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				mockBidderParamValidator{},
+				ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -5105,7 +4254,7 @@ func TestValidateStoredResp(t *testing.T) {
 	deps := &endpointDeps{
 		fakeUUIDGenerator{},
 		&nobidExchange{},
-		mockBidderParamValidator{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 		&mockStoredReqFetcher{},
 		empty_fetcher.EmptyFetcher{},
 		empty_fetcher.EmptyFetcher{},
@@ -5128,6 +4277,8 @@ func TestValidateStoredResp(t *testing.T) {
 	testCases := []struct {
 		description               string
 		givenRequestWrapper       *openrtb_ext.RequestWrapper
+		givenHttpRequest          *http.Request
+		givenAccount              *config.Account
 		expectedErrorList         []error
 		hasStoredAuctionResponses bool
 		storedBidResponses        stored_responses.ImpBidderStoredResp
@@ -5669,7 +4820,7 @@ func TestValidateStoredResp(t *testing.T) {
 
 	for _, test := range testCases {
 		t.Run(test.description, func(t *testing.T) {
-			errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false)
+			errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false)
 			assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
 		})
 	}
@@ -5921,7 +5072,7 @@ func TestParseRequestMultiBid(t *testing.T) {
 			deps := &endpointDeps{
 				fakeUUIDGenerator{},
 				&warningsCheckExchange{},
-				mockBidderParamValidator{},
+				ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
 				&mockStoredReqFetcher{},
 				empty_fetcher.EmptyFetcher{},
 				empty_fetcher.EmptyFetcher{},
@@ -6108,3 +5259,726 @@ func TestValidateAliases(t *testing.T) {
 func fakeNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) {
 	return openrtb_ext.BidderName(strings.ToLower(name)), true
 }
+
+func TestValidateOrFillCookieDeprecation(t *testing.T) {
+	type args struct {
+		httpReq *http.Request
+		req     *openrtb_ext.RequestWrapper
+		account config.Account
+	}
+	tests := []struct {
+		name          string
+		args          args
+		wantDeviceExt json.RawMessage
+		wantErr       error
+	}{
+		{
+			name:          "account-nil",
+			wantDeviceExt: nil,
+			wantErr:       nil,
+		},
+		{
+			name: "cookie-deprecation-not-enabled",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{},
+				},
+			},
+			wantDeviceExt: nil,
+			wantErr:       nil,
+		},
+		{
+			name: "cookie-deprecation-disabled-explicitly",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: false,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: nil,
+			wantErr:       nil,
+		},
+		{
+			name: "cookie-deprecation-enabled-header-not-present-in-request",
+			args: args{
+				httpReq: &http.Request{},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: nil,
+			wantErr:       nil,
+		},
+		{
+			name: "header-present-request-device-nil",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`),
+			wantErr:       nil,
+		},
+		{
+			name: "header-present-request-device-ext-nil",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Ext: nil,
+						},
+					},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`),
+			wantErr:       nil,
+		},
+		{
+			name: "header-present-request-device-ext-not-nil",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Ext: json.RawMessage(`{"foo":"bar"}`),
+						},
+					},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1","foo":"bar"}`),
+			wantErr:       nil,
+		},
+		{
+			name: "header-present-with-length-more-than-100",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Ext: json.RawMessage(`{"foo":"bar"}`),
+						},
+					},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{"foo":"bar"}`),
+			wantErr: &errortypes.Warning{
+				Message:     "request.device.ext.cdep must not exceed 100 characters",
+				WarningCode: errortypes.SecCookieDeprecationLenWarningCode,
+			},
+		},
+		{
+			name: "header-present-request-device-ext-cdep-present",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Ext: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`),
+						},
+					},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`),
+			wantErr:       nil,
+		},
+		{
+			name: "header-present-request-device-ext-invalid",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
+				},
+				req: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Device: &openrtb2.Device{
+							Ext: json.RawMessage(`{`),
+						},
+					},
+				},
+				account: config.Account{
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+							},
+						},
+					},
+				},
+			},
+			wantDeviceExt: json.RawMessage(`{`),
+			wantErr: &errortypes.FailedToUnmarshal{
+				Message: "expects \" or n, but found \x00",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := validateOrFillCookieDeprecation(tt.args.httpReq, tt.args.req, &tt.args.account)
+			assert.Equal(t, tt.wantErr, err)
+			if tt.args.req != nil {
+				err := tt.args.req.RebuildRequest()
+				assert.NoError(t, err)
+			}
+			if tt.wantDeviceExt == nil {
+				if tt.args.req != nil && tt.args.req.Device != nil {
+					assert.Nil(t, tt.args.req.Device.Ext)
+				}
+			} else {
+				assert.Equal(t, string(tt.wantDeviceExt), string(tt.args.req.Device.Ext))
+			}
+		})
+	}
+}
+
+func TestSetGPCImplicitly(t *testing.T) {
+	testCases := []struct {
+		description  string
+		header       string
+		regs         *openrtb2.Regs
+		expectError  bool
+		expectedRegs *openrtb2.Regs
+	}{
+		{
+			description: "regs_ext_gpc_not_set_and_header_is_1",
+			header:      "1",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+		},
+		{
+			description: "sec_gpc_header_not_set_gpc_should_not_be_modified",
+			header:      "",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+		},
+		{
+			description: "sec_gpc_header_set_to_2_gpc_should_not_be_modified",
+			header:      "2",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+		},
+		{
+			description: "sec_gpc_header_set_to_1_and_regs_ext_contains_other_data",
+			header:      "1",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{"some_other_field":"some_value"}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"some_other_field":"some_value","gpc":"1"}`),
+			},
+		},
+		{
+			description: "regs_ext_gpc_not_set_and_header_not_set",
+			header:      "",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+		},
+		{
+			description: "regs_ext_gpc_not_set_and_header_not_1",
+			header:      "0",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{}`),
+			},
+		},
+		{
+			description: "regs_ext_gpc_is_1_and_header_is_1",
+			header:      "1",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+		},
+		{
+			description: "regs_ext_gpc_is_1_and_header_not_1",
+			header:      "0",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+		},
+		{
+			description: "regs_ext_other_data_and_header_is_1",
+			header:      "1",
+			regs: &openrtb2.Regs{
+				Ext: []byte(`{"other":"value"}`),
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"other":"value","gpc":"1"}`),
+			},
+		},
+		{
+			description: "regs_nil_and_header_is_1",
+			header:      "1",
+			regs:        nil,
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: []byte(`{"gpc":"1"}`),
+			},
+		},
+		{
+			description:  "regs_nil_and_header_not_set",
+			header:       "",
+			regs:         nil,
+			expectError:  false,
+			expectedRegs: nil,
+		},
+		{
+			description: "regs_ext_is_nil_and_header_not_set",
+			header:      "",
+			regs: &openrtb2.Regs{
+				Ext: nil,
+			},
+			expectError: false,
+			expectedRegs: &openrtb2.Regs{
+				Ext: nil,
+			},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.description, func(t *testing.T) {
+			httpReq := &http.Request{
+				Header: http.Header{
+					http.CanonicalHeaderKey("Sec-GPC"): []string{test.header},
+				},
+			}
+
+			r := &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: test.regs,
+				},
+			}
+
+			err := setGPCImplicitly(httpReq, r)
+
+			if test.expectError {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.NoError(t, r.RebuildRequest())
+			if test.expectedRegs == nil {
+				assert.Nil(t, r.BidRequest.Regs)
+			} else if test.expectedRegs.Ext == nil {
+				assert.Nil(t, r.BidRequest.Regs.Ext)
+			} else {
+				assert.JSONEq(t, string(test.expectedRegs.Ext), string(r.BidRequest.Regs.Ext))
+			}
+		})
+	}
+}
+
+func TestValidateRequestCookieDeprecation(t *testing.T) {
+	testCases :=
+		[]struct {
+			name         string
+			givenAccount *config.Account
+			httpReq      *http.Request
+			reqWrapper   *openrtb_ext.RequestWrapper
+			wantErrs     []error
+			wantCDep     string
+		}{
+			{
+				name: "header-with-length-less-than-100",
+				httpReq: func() *http.Request {
+					req := httptest.NewRequest("POST", "/openrtb2/auction", nil)
+					req.Header.Set(secCookieDeprecation, "sample-value")
+					return req
+				}(),
+				givenAccount: &config.Account{
+					ID: "1",
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+								TTLSec:  86400,
+							},
+						},
+					},
+				},
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID:  "Some-ID",
+						App: &openrtb2.App{},
+						Imp: []openrtb2.Imp{
+							{
+								ID: "Some-Imp-ID",
+								Banner: &openrtb2.Banner{
+									Format: []openrtb2.Format{
+										{
+											W: 600,
+											H: 500,
+										},
+										{
+											W: 300,
+											H: 600,
+										},
+									},
+								},
+								Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`),
+							},
+						},
+					},
+				},
+				wantErrs: []error{},
+				wantCDep: "sample-value",
+			},
+			{
+				name: "header-with-length-more-than-100",
+				httpReq: func() *http.Request {
+					req := httptest.NewRequest("POST", "/openrtb2/auction", nil)
+					req.Header.Set(secCookieDeprecation, "zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa")
+					return req
+				}(),
+				givenAccount: &config.Account{
+					ID: "1",
+					Privacy: config.AccountPrivacy{
+						PrivacySandbox: config.PrivacySandbox{
+							CookieDeprecation: config.CookieDeprecation{
+								Enabled: true,
+								TTLSec:  86400,
+							},
+						},
+					},
+				},
+				reqWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						ID:  "Some-ID",
+						App: &openrtb2.App{},
+						Imp: []openrtb2.Imp{
+							{
+								ID: "Some-Imp-ID",
+								Banner: &openrtb2.Banner{
+									Format: []openrtb2.Format{
+										{
+											W: 600,
+											H: 500,
+										},
+										{
+											W: 300,
+											H: 600,
+										},
+									},
+								},
+								Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`),
+							},
+						},
+					},
+				},
+				wantErrs: []error{
+					&errortypes.Warning{
+						Message:     "request.device.ext.cdep must not exceed 100 characters",
+						WarningCode: errortypes.SecCookieDeprecationLenWarningCode,
+					},
+				},
+				wantCDep: "",
+			},
+		}
+
+	deps := &endpointDeps{
+		fakeUUIDGenerator{},
+		&warningsCheckExchange{},
+		ortb.NewRequestValidator(openrtb_ext.BuildBidderMap(), map[string]string{}, mockBidderParamValidator{}),
+		&mockStoredReqFetcher{},
+		empty_fetcher.EmptyFetcher{},
+		&mockAccountFetcher{},
+		&config.Configuration{},
+		&metricsConfig.NilMetricsEngine{},
+		analyticsBuild.New(&config.Analytics{}),
+		map[string]string{},
+		false,
+		[]byte{},
+		openrtb_ext.BuildBidderMap(),
+		nil,
+		nil,
+		hardcodedResponseIPValidator{response: true},
+		empty_fetcher.EmptyFetcher{},
+		hooks.EmptyPlanBuilder{},
+		nil,
+		openrtb_ext.NormalizeBidderName,
+	}
+
+	for _, test := range testCases {
+		errs := deps.validateRequest(test.givenAccount, test.httpReq, test.reqWrapper, false, false, stored_responses.ImpBidderStoredResp{}, false)
+		assert.Equal(t, test.wantErrs, errs)
+		test.reqWrapper.RebuildRequest()
+		deviceExt, err := test.reqWrapper.GetDeviceExt()
+		assert.NoError(t, err)
+		assert.Equal(t, test.wantCDep, deviceExt.GetCDep())
+	}
+}
+
+func TestSetSecBrowsingTopicsImplicitly(t *testing.T) {
+	type args struct {
+		httpReq *http.Request
+		r       *openrtb_ext.RequestWrapper
+		account *config.Account
+	}
+	tests := []struct {
+		name     string
+		args     args
+		wantUser *openrtb2.User
+	}{
+		{
+			name: "empty HTTP request, no change in user data",
+			args: args{
+				httpReq: &http.Request{},
+				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
+				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
+			},
+			wantUser: nil,
+		},
+		{
+			name: "valid topic in request but topicsdomain not configured by host, no change in user data",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{
+						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"},
+					},
+				},
+				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
+				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: ""}}},
+			},
+			wantUser: nil,
+		},
+		{
+			name: "valid topic in request and topicsdomain configured by host, topics copied to user data",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{
+						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"},
+					},
+				},
+				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
+				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
+			},
+			wantUser: &openrtb2.User{
+				Data: []openrtb2.Data{
+					{
+						Name: "ads.pubmatic.com",
+						Segment: []openrtb2.Segment{
+							{
+								ID: "1",
+							},
+						},
+						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
+					},
+				},
+			},
+		},
+		{
+			name: "valid empty topic in request, no change in user data",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{
+						secBrowsingTopics: []string{"();p=P0000000000000000000000000000000"},
+					},
+				},
+				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
+				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
+			},
+			wantUser: nil,
+		},
+		{
+			name: "request with a few valid topics (including duplicate topics, segIDs, matching segtax, segclass, etc) and a few invalid topics(different invalid format), only valid and unique topics copied/merged to/with user data",
+			args: args{
+				httpReq: &http.Request{
+					Header: http.Header{
+						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, (1 2);v=chrome.1:1:2,(4);v=chrome.1:1:2,();p=P0000000000,(4);v=chrome.1, 5);v=chrome.1, (6;v=chrome.1, ();v=chrome.1, (	);v=chrome.1, (1);v=chrome.1:1:2, (1 2 4		6 7			4567	  ) ; v=chrome.1: 2 : 3,();p=P0000000000"},
+					},
+				},
+				r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
+					User: &openrtb2.User{
+						Data: []openrtb2.Data{
+							{
+								Name: "chrome.com",
+								Segment: []openrtb2.Segment{
+									{ID: "1"},
+								},
+								Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`),
+							},
+							{
+								Name: "ads.pubmatic.com",
+								Segment: []openrtb2.Segment{
+									{ID: "1"},
+									{ID: "3"},
+								},
+								Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
+							},
+						},
+					},
+				}},
+				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
+			},
+			wantUser: &openrtb2.User{
+				Data: []openrtb2.Data{
+					{
+						Name: "chrome.com",
+						Segment: []openrtb2.Segment{
+							{ID: "1"},
+						},
+						Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`),
+					},
+					{
+						Name: "ads.pubmatic.com",
+						Segment: []openrtb2.Segment{
+							{ID: "1"},
+							{ID: "2"},
+							{ID: "3"},
+							{ID: "4"},
+						},
+						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
+					},
+					{
+						Name: "ads.pubmatic.com",
+						Segment: []openrtb2.Segment{
+							{ID: "1"},
+							{ID: "2"},
+							{ID: "4"},
+							{ID: "6"},
+							{ID: "7"},
+							{ID: "4567"},
+						},
+						Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`),
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			setSecBrowsingTopicsImplicitly(tt.args.httpReq, tt.args.r, tt.args.account)
+
+			// sequence is not guaranteed we're using a map to filter segids
+			sortUserData(tt.wantUser)
+			sortUserData(tt.args.r.User)
+			assert.Equal(t, tt.wantUser, tt.args.r.User, tt.name)
+		})
+	}
+}
+
+func sortUserData(user *openrtb2.User) {
+	if user != nil {
+		sort.Slice(user.Data, func(i, j int) bool {
+			if user.Data[i].Name == user.Data[j].Name {
+				return string(user.Data[i].Ext) < string(user.Data[j].Ext)
+			}
+			return user.Data[i].Name < user.Data[j].Name
+		})
+		for g := range user.Data {
+			sort.Slice(user.Data[g].Segment, func(i, j int) bool {
+				return user.Data[g].Segment[i].ID < user.Data[g].Segment[j].ID
+			})
+		}
+	}
+}
diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json
index 8dc19f6f24d..30d8b9ca240 100644
--- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json
+++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-ccpa-through-query.json
@@ -74,9 +74,7 @@
       }
     ],
     "regs": {
-      "ext": {
-        "us_privacy": "1YYY"
-      }
+      "us_privacy": "1YYY"
     },
     "ext": {
       "prebid": {
diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json
index 4003abf99cb..8c232192a63 100644
--- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json
+++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-legacy-tcf2-consent-through-query.json
@@ -74,14 +74,10 @@
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 1
-      }
+      "gdpr": 1
     },
     "user": {
-      "ext": {
-        "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
-      }
+      "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
     },
     "ext": {
       "prebid": {
diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json
index c6389dadc29..0249bbc3f96 100644
--- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json
+++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf1-consent-through-query.json
@@ -79,9 +79,7 @@
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 1
-      }
+      "gdpr": 1
     },
     "ext": {
       "prebid": {
diff --git a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json
index b62a745b1bf..6f0f5780b43 100644
--- a/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json
+++ b/endpoints/openrtb2/sample-requests/amp/consent-through-query/gdpr-tcf2-consent-through-query.json
@@ -74,14 +74,10 @@
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 1
-      }
+      "gdpr": 1
     },
     "user": {
-      "ext": {
-        "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
-      }
+      "consent": "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
     },
     "ext": {
       "prebid": {
diff --git a/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json
new file mode 100644
index 00000000000..766b7fc6ba9
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/amp/valid-supplementary/ortb-2.5-to-2.6-upconvert.json
@@ -0,0 +1,306 @@
+{
+  "description": "Amp request with all 2.5 ext fields that were moved into 2.6 ortb fields",
+  "query": "tag_id=101",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 15
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "/19968336/header-bid-tag-0",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              }
+            },
+            "is_rewarded_inventory": 1
+          }
+        }
+      }
+    ],
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1YYY"
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "some-consent-string",
+        "eids": [
+          {
+              "source": "source",
+              "uids": [
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  },
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  }
+              ],
+              "ext": {}
+          }
+        ]
+      }
+    },
+    "source": {
+      "ext": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+              {
+                  "asi": "whatever.com",
+                  "sid": "1234",
+                  "rid": "123-456-7890",
+                  "hp": 1
+              }
+          ],
+          "ver": "2.0"
+      }
+      }
+    }
+  },
+  "expectedValidatedBidRequest": {
+    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+    "site": {
+      "page": "prebid.org",
+      "ext": {
+        "amp": 1
+      }
+    },
+    "device": {
+      "ip": "192.0.2.1"
+    },
+    "at": 1,
+    "imp": [
+      {
+        "id": "/19968336/header-bid-tag-0",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              }
+            }
+          }
+        },
+        "secure": 1,
+        "rwdd": 1
+      }
+    ],
+    "regs": {
+      "gdpr": 1,
+      "us_privacy": "1YYY"
+    },
+    "user": {
+      "consent": "some-consent-string",
+      "eids": [
+        {
+            "source": "source",
+            "uids": [
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                },
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                }
+            ],
+            "ext": {}
+        }
+      ]
+    },
+    "source": {
+      "schain": {
+        "complete": 1,
+        "nodes": [
+            {
+                "asi": "whatever.com",
+                "sid": "1234",
+                "rid": "123-456-7890",
+                "hp": 1
+            }
+        ],
+        "ver": "2.0"
+      }
+    },
+    "ext": {
+      "prebid": {
+        "cache": {
+          "bids": {}
+        },
+        "channel": {
+          "name": "amp",
+          "version": ""
+        },
+        "targeting": {
+          "pricegranularity": {
+            "precision": 2,
+            "ranges": [
+              {
+                "min": 0,
+                "max": 20,
+                "increment": 0.1
+              }
+            ]
+          },
+          "includewinners": true,
+          "includebidderkeys": true,
+          "mediatypepricegranularity": {}
+        }
+      }
+    }
+  },
+  "expectedMockBidderRequests": {
+    "appnexus": {
+      "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
+      "site": {
+        "page": "prebid.org",
+        "ext": {
+          "amp": 1
+        }
+      },
+      "device": {
+        "ip": "192.0.2.1"
+      },
+      "at": 1,
+      "imp": [
+        {
+          "id": "/19968336/header-bid-tag-0",
+          "banner": {
+            "format": [
+              {
+                "w": 300,
+                "h": 600
+              }
+            ]
+          },
+          "ext": {
+            "bidder": {
+              "placementId": 12883451
+            },
+            "prebid": {
+              "is_rewarded_inventory": 1
+            }
+          },
+          "secure": 1
+        }
+      ],
+      "regs": {
+        "ext": {
+          "gdpr": 1,
+          "us_privacy": "1YYY"
+        }
+      },
+      "user": {
+        "ext": {
+          "consent": "some-consent-string",
+          "eids": [
+            {
+                "source": "source",
+                "uids": [
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    },
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    }
+                ],
+                "ext": {}
+            }
+          ]
+        }
+      },
+      "source": {
+        "ext": {
+          "schain": {
+            "complete": 1,
+            "nodes": [
+                {
+                    "asi": "whatever.com",
+                    "sid": "1234",
+                    "rid": "123-456-7890",
+                    "hp": 1
+                }
+            ],
+            "ver": "2.0"
+          }
+        }
+      },
+      "ext": {
+        "prebid": {
+          "channel": {
+            "name": "amp",
+            "version": ""
+          }
+        }
+      }
+    }
+  },
+  "expectedAmpResponse": {
+    "targeting": {
+      "hb_bidder": "appnexus",
+      "hb_bidder_appnexus": "appnexus",
+      "hb_cache_host": "www.pbcserver.com",
+      "hb_cache_host_appnex": "www.pbcserver.com",
+      "hb_cache_id": "0",
+      "hb_cache_id_appnexus": "0",
+      "hb_cache_path": "/pbcache/endpoint",
+      "hb_cache_path_appnex": "/pbcache/endpoint",
+      "hb_pb": "15.00",
+      "hb_pb_appnexus": "15.00"
+    },
+    "ortb2": {
+      "ext": {
+        "warnings": {
+          "general": [
+            {
+              "code": 10002,
+              "message": "debug turned off for account"
+            }
+          ]
+        }
+      }
+    }
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json b/endpoints/openrtb2/sample-requests/blocked/blocked-app.json
similarity index 96%
rename from endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json
rename to endpoints/openrtb2/sample-requests/blocked/blocked-app.json
index 120fcec08f4..88e0ed43496 100644
--- a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json
+++ b/endpoints/openrtb2/sample-requests/blocked/blocked-app.json
@@ -1,7 +1,7 @@
 {
-  "description": "This is a perfectly valid request except that it comes from a blacklisted App",
+  "description": "This is a perfectly valid request except that it comes from a blocked app",
   "config": {
-    "blacklistedApps": ["spam_app"]
+    "blockedApps": ["spam_app"]
   },
   "mockBidRequest": {
     "id": "some-request-id",
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
deleted file mode 100644
index dc15410a290..00000000000
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
-  "description": "Invalid GDPR value in regs field",
-  "mockBidRequest": {
-    "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
-    "site": {
-      "page": "prebid.org",
-      "publisher": {
-        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
-      }
-    },
-    "source": {
-      "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5"
-    },
-    "tmax": 1000,
-    "imp": [
-      {
-        "id": "/19968336/header-bid-tag-0",
-        "ext": {
-          "appnexus": {
-            "placementId": 12883451
-          }
-        },
-        "banner": {
-          "format": [
-            {
-              "w": 300,
-              "h": 250
-            },
-            {
-              "w": 300,
-              "h": 300
-            }
-          ]
-        }
-      }
-    ],
-    "regs": {
-      "ext": {
-        "gdpr": "foo"
-      }
-    },
-    "user": {
-      "ext": {}
-    }
-  },
-  "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: gdpr must be an integer\n"
-}
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
index 7ab2631b701..4a513f703b1 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-malformed.json
@@ -42,5 +42,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: expect { or n, but found "
+  "expectedErrorMessage": "Invalid request: req.regs.ext is invalid: expect { or n, but found "
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json
similarity index 86%
rename from endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
rename to endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json
index 03e789eef86..40b7281d572 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-gdpr-invalid.json
@@ -35,14 +35,12 @@
       }
     ],
     "regs": {
-      "ext": {
-        "gdpr": 2
-      }
+      "gdpr": 2
     },
     "user": {
       "ext": {}
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1\n"
+  "expectedErrorMessage": "Invalid request: request.regs.gdpr must be either 0 or 1\n"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json
similarity index 63%
rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json
rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json
index 910e9650d75..902a2d9c1b6 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-id-empty.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-source-empty.json
@@ -1,5 +1,5 @@
 {
-  "description": "Bid request where a request.user.ext.eids.uids array element is missing its id field",
+  "description": "Bid request with user.eids array element that does not contain source field",
   "mockBidRequest": {
     "id": "anyRequestID",
     "site": {
@@ -29,14 +29,13 @@
     }],
     "tmax": 1000,
     "user": {
-      "ext": {
-        "eids": [{
-          "source": "source1",
-          "uids": [{}]
+      "eids": [{
+        "uids": [{
+          "id": "A"
         }]
-      }
+      }]
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids[0] missing required field: \"id\"\n"
+  "expectedErrorMessage": "Invalid request: request.user.eids[0] missing required field: \"source\"\n"
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json
similarity index 62%
rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json
rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json
index 3a451ecbd76..c8eb07aa335 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-source-empty.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-id-empty.json
@@ -1,5 +1,5 @@
 {
-  "description": "Bid request with user.ext.eids array element array element that does not contain source field",
+  "description": "Bid request where a request.user.eids.uids array element is missing its id field",
   "mockBidRequest": {
     "id": "anyRequestID",
     "site": {
@@ -29,15 +29,12 @@
     }],
     "tmax": 1000,
     "user": {
-      "ext": {
-        "eids": [{
-          "uids": [{
-            "id": "A"
-          }]
-        }]
-      }
+      "eids": [{
+        "source": "source1",
+        "uids": [{}]
+      }]
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.user.ext.eids[0] missing required field: \"source\"\n"
+  "expectedErrorMessage": "Invalid request: request.user.eids[0].uids[0] missing required field: \"id\"\n"
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json
similarity index 70%
rename from endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json
rename to endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json
index eed386b4c7d..3e55c33b849 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-eids-uids-missing.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-eids-uids-missing.json
@@ -1,5 +1,5 @@
 {
-  "description": "Bid request with user.ext.eids array element array element that does not contain uids",
+  "description": "Bid request with user.eids array element array element that does not contain uids",
   "mockBidRequest": {
     "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5",
     "site": {
@@ -37,13 +37,11 @@
       }
     },
     "user": {
-      "ext": {
-        "eids": [{
-          "source": "source1"
-        }]
-      }
+      "eids": [{
+        "source": "source1"
+      }]
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.user.ext.eids[0].uids must contain at least one element or be undefined\n"
+  "expectedErrorMessage": "Invalid request: request.user.eids[0].uids must contain at least one element or be undefined\n"
 }
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
index af04627c3a9..222ffb993b7 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-ext-consent-int.json
@@ -46,5 +46,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 1"
+  "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 1"
 }
diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
index b710d589ea5..ae9d72c8682 100644
--- a/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
+++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-gdpr-consent-invalid.json
@@ -41,5 +41,5 @@
     }
   },
   "expectedReturnCode": 400,
-  "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: expects \" or n, but found 2"
+  "expectedErrorMessage": "Invalid request: req.user.ext is invalid: expects \" or n, but found 2"
 }
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json
new file mode 100644
index 00000000000..0f85f904166
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/device-sua.json
@@ -0,0 +1,100 @@
+{
+  "description": "Bid request defines an valid request.device.sua value",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 0.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "publisher": {
+        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
+      }
+    },
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 300
+            }
+          ]
+        }
+      }
+    ],
+    "device": {
+      "ua": "Mozilla",
+      "geo": {
+        "lat": 123.456,
+        "lon": 678.90,
+        "zip": "90210"
+      },
+      "sua": {
+        "browsers": [
+          {
+            "brand": "MS",
+            "ext": {}
+          },
+          {
+            "brand": "MS",
+            "ext": {}
+          }
+        ],
+        "platform": {
+          "brand": "MS",
+          "ext": {}
+        },
+        "model": "mac"
+      },
+      "dnt": 1,
+      "lmt": 1
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 0,
+            "ext": {
+              "origbidcpm": 0,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json
new file mode 100644
index 00000000000..3ff235c4b30
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.5-to-2.6-upconvert.json
@@ -0,0 +1,393 @@
+{
+  "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 15
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ],
+    "bidderInfoOverrides": {
+      "appnexus": {
+        "openrtb": {
+          "version": "2.5"
+        }
+      },
+      "rubicon": {
+        "openrtb": {
+          "version": "2.6"
+        }
+      }
+    }
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              },
+              "rubicon": {
+                "accountId": 1,
+                "siteId": 2,
+                "zoneId": 3
+              }
+            },
+            "is_rewarded_inventory": 1
+          }
+        }
+      }
+    ],
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1YYY"
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "some-consent-string",
+        "eids": [
+          {
+              "source": "source",
+              "uids": [
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  },
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  }
+              ],
+              "ext": {}
+          }
+        ]
+      }
+    },
+    "source": {
+      "ext": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+              {
+                  "asi": "whatever.com",
+                  "sid": "1234",
+                  "rid": "123-456-7890",
+                  "hp": 1
+              }
+          ],
+          "ver": "2.0"
+        }
+      }
+    },
+    "ext": {}
+  },
+  "expectedValidatedBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "ext": {
+        "amp": 0
+      }
+    },
+    "at": 1,
+    "device": {
+      "ip": "192.0.2.1"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              },
+              "rubicon": {
+                "accountId": 1,
+                "siteId": 2,
+                "zoneId": 3
+              }
+            }
+          }
+        },
+        "secure": 1,
+        "rwdd": 1
+      }
+    ],
+    "regs": {
+      "gdpr": 1,
+      "us_privacy": "1YYY"
+    },
+    "user": {
+      "consent": "some-consent-string",
+      "eids": [
+        {
+            "source": "source",
+            "uids": [
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                },
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                }
+            ],
+            "ext": {}
+        }
+      ]
+    },
+    "source": {
+      "schain": {
+        "complete": 1,
+        "nodes": [
+            {
+                "asi": "whatever.com",
+                "sid": "1234",
+                "rid": "123-456-7890",
+                "hp": 1
+            }
+        ],
+        "ver": "2.0"
+      }
+    }
+  },
+  "expectedMockBidderRequests": {
+    "appnexus": {
+      "id": "some-request-id",
+      "site": {
+        "page": "prebid.org",
+        "ext": {
+          "amp": 0
+        }
+      },
+      "at": 1,
+      "device": {
+        "ip": "192.0.2.1"
+      },
+      "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "is_rewarded_inventory": 1
+          }
+        },
+        "secure": 1
+      }],
+      "regs": {
+        "ext": {
+          "gdpr": 1,
+          "us_privacy": "1YYY"
+        }
+      },
+      "source": {
+        "ext": {
+          "schain": {
+            "complete": 1,
+            "nodes": [
+                {
+                    "asi": "whatever.com",
+                    "sid": "1234",
+                    "rid": "123-456-7890",
+                    "hp": 1
+                }
+            ],
+            "ver": "2.0"
+          }
+        }
+      },
+      "user": {
+        "ext": {
+          "consent": "some-consent-string",
+          "eids": [
+            {
+                "source": "source",
+                "uids": [
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    },
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    }
+                ],
+                "ext": {}
+            }
+          ]
+        }
+      }
+    },
+    "rubicon": {
+      "id": "some-request-id",
+      "site": {
+        "page": "prebid.org",
+        "ext": {
+          "amp": 0
+        }
+      },
+      "at": 1,
+      "device": {
+        "ip": "192.0.2.1"
+      },
+      "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "accountId": 1,
+            "siteId": 2,
+            "zoneId": 3
+          }
+        },
+        "secure": 1,
+        "rwdd": 1
+      }],
+      "regs": {
+        "gdpr": 1,
+        "us_privacy": "1YYY"
+      },
+      "source": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+              {
+                  "asi": "whatever.com",
+                  "sid": "1234",
+                  "rid": "123-456-7890",
+                  "hp": 1
+              }
+          ],
+          "ver": "2.0"
+        }
+      },
+      "user": {
+        "consent": "some-consent-string",
+        "eids": [
+          {
+              "source": "source",
+              "uids": [
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  },
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  }
+              ],
+              "ext": {}
+          }
+        ]
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 15,
+            "ext": {
+              "origbidcpm": 15,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 1.00,
+            "ext": {
+              "origbidcpm": 1.00,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test-bid-id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json
new file mode 100644
index 00000000000..28ba8a707e0
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/ortb-2.6-to-2.5-downconvert.json
@@ -0,0 +1,417 @@
+{
+  "description": "Request with all 2.5 ext fields that were moved into 2.6 ortb fields",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 15
+      },
+      {
+        "bidderName": "rubicon",
+        "currency": "USD",
+        "price": 1.00
+      }
+    ],
+    "bidderInfoOverrides": {
+      "appnexus": {
+        "openrtb": {
+          "version": "2.5"
+        }
+      },
+      "rubicon": {
+        "openrtb": {
+          "version": "2.6"
+        }
+      }
+    }
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "inventorypartnerdomain": "any-domain"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              },
+              "rubicon": {
+                "accountId": 1,
+                "siteId": 2,
+                "zoneId": 3
+              }
+            },
+            "is_rewarded_inventory": 1
+          }
+        },
+        "refresh": {
+          "count": 10
+        }
+      }
+    ],
+    "regs": {
+      "ext": {
+        "gdpr": 1,
+        "us_privacy": "1YYY"
+      }
+    },
+    "user": {
+      "ext": {
+        "consent": "some-consent-string",
+        "eids": [
+          {
+              "source": "source",
+              "uids": [
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  },
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  }
+              ],
+              "ext": {}
+          }
+        ]
+      }
+    },
+    "source": {
+      "ext": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+              {
+                  "asi": "whatever.com",
+                  "sid": "1234",
+                  "rid": "123-456-7890",
+                  "hp": 1
+              }
+          ],
+          "ver": "2.0"
+        }
+      }
+    },
+    "ext": {},
+    "cattax": 20,
+    "acat": ["any-acat"]
+  },
+  "expectedValidatedBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "inventorypartnerdomain": "any-domain",
+      "ext": {
+        "amp": 0
+      }
+    },
+    "at": 1,
+    "device": {
+      "ip": "192.0.2.1"
+    },
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "prebid": {
+            "bidder": {
+              "appnexus": {
+                "placementId": 12883451
+              },
+              "rubicon": {
+                "accountId": 1,
+                "siteId": 2,
+                "zoneId": 3
+              }
+            }
+          }
+        },
+        "secure": 1,
+        "rwdd": 1,
+        "refresh": {
+          "count": 10
+        }
+      }
+    ],
+    "regs": {
+      "gdpr": 1,
+      "us_privacy": "1YYY"
+    },
+    "user": {
+      "consent": "some-consent-string",
+      "eids": [
+        {
+            "source": "source",
+            "uids": [
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                },
+                {
+                    "id": "1",
+                    "atype": 1,
+                    "ext": {}
+                }
+            ],
+            "ext": {}
+        }
+      ]
+    },
+    "source": {
+      "schain": {
+        "complete": 1,
+        "nodes": [
+            {
+                "asi": "whatever.com",
+                "sid": "1234",
+                "rid": "123-456-7890",
+                "hp": 1
+            }
+        ],
+        "ver": "2.0"
+      }
+    },
+    "cattax": 20,
+    "acat": ["any-acat"]
+  },
+  "expectedMockBidderRequests": {
+    "appnexus": {
+      "id": "some-request-id",
+      "site": {
+        "page": "prebid.org",
+        "inventorypartnerdomain": "any-domain",
+        "ext": {
+          "amp": 0
+        }
+      },
+      "at": 1,
+      "device": {
+        "ip": "192.0.2.1"
+      },
+      "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "placementId": 12883451
+          },
+          "prebid": {
+            "is_rewarded_inventory": 1
+          }
+        },
+        "refresh": {
+          "count": 10
+        },
+        "secure": 1
+      }],
+      "regs": {
+        "ext": {
+          "gdpr": 1,
+          "us_privacy": "1YYY"
+        }
+      },
+      "source": {
+        "ext": {
+          "schain": {
+            "complete": 1,
+            "nodes": [
+                {
+                    "asi": "whatever.com",
+                    "sid": "1234",
+                    "rid": "123-456-7890",
+                    "hp": 1
+                }
+            ],
+            "ver": "2.0"
+          }
+        }
+      },
+      "user": {
+        "ext": {
+          "consent": "some-consent-string",
+          "eids": [
+            {
+                "source": "source",
+                "uids": [
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    },
+                    {
+                        "id": "1",
+                        "atype": 1,
+                        "ext": {}
+                    }
+                ],
+                "ext": {}
+            }
+          ]
+        }
+      },
+      "cattax": 20,
+      "acat": ["any-acat"]
+    },
+    "rubicon": {
+      "id": "some-request-id",
+      "site": {
+        "page": "prebid.org",
+        "inventorypartnerdomain": "any-domain",
+        "ext": {
+          "amp": 0
+        }
+      },
+      "at": 1,
+      "device": {
+        "ip": "192.0.2.1"
+      },
+      "imp": [
+      {
+        "id": "some-impression-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "accountId": 1,
+            "siteId": 2,
+            "zoneId": 3
+          }
+        },
+        "secure": 1,
+        "refresh": {
+          "count": 10
+        },
+        "rwdd": 1
+      }],
+      "regs": {
+        "gdpr": 1,
+        "us_privacy": "1YYY"
+      },
+      "source": {
+        "schain": {
+          "complete": 1,
+          "nodes": [
+              {
+                  "asi": "whatever.com",
+                  "sid": "1234",
+                  "rid": "123-456-7890",
+                  "hp": 1
+              }
+          ],
+          "ver": "2.0"
+        }
+      },
+      "user": {
+        "consent": "some-consent-string",
+        "eids": [
+          {
+              "source": "source",
+              "uids": [
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  },
+                  {
+                      "id": "1",
+                      "atype": 1,
+                      "ext": {}
+                  }
+              ],
+              "ext": {}
+          }
+        ]
+      },
+      "cattax": 20,
+      "acat": ["any-acat"]
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 15,
+            "ext": {
+              "origbidcpm": 15,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      },
+      {
+        "bid": [
+          {
+            "id": "rubicon-bid",
+            "impid": "some-impression-id",
+            "price": 1.00,
+            "ext": {
+              "origbidcpm": 1.00,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "rubicon"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "rubicon"
+      }
+    ],
+    "bidid": "test-bid-id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
\ No newline at end of file
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json
new file mode 100644
index 00000000000..c25f7ca1c47
--- /dev/null
+++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/source-schain.json
@@ -0,0 +1,91 @@
+{
+  "description": "Bid request defines a valid request.source.schain.nodes value",
+  "config": {
+    "mockBidders": [
+      {
+        "bidderName": "appnexus",
+        "currency": "USD",
+        "price": 0.00
+      }
+    ]
+  },
+  "mockBidRequest": {
+    "id": "some-request-id",
+    "site": {
+      "page": "prebid.org",
+      "publisher": {
+        "id": "a3de7af2-a86a-4043-a77b-c7e86744155e"
+      }
+    },
+    "tmax": 1000,
+    "imp": [
+      {
+        "id": "some-impression-id",
+        "ext": {
+          "appnexus": {
+            "placementId": 12883451
+          }
+        },
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 300
+            }
+          ]
+        }
+      }
+    ],
+    "source": {
+      "fd": 1,
+      "tid": "abc123",
+      "pchain": "tag_placement",
+      "schain": {
+        "complete": 1,
+        "nodes": [
+          {
+            "asi": "asi",
+            "sid": "sid",
+            "rid": "rid",
+            "ext": {}
+          }
+        ],
+        "ver": "ver",
+        "ext": {}
+      }
+    }
+  },
+  "expectedBidResponse": {
+    "id": "some-request-id",
+    "seatbid": [
+      {
+        "bid": [
+          {
+            "id": "appnexus-bid",
+            "impid": "some-impression-id",
+            "price": 0,
+            "ext": {
+              "origbidcpm": 0,
+              "origbidcur": "USD",
+              "prebid": {
+                "meta": {
+                  "adaptercode": "appnexus"
+                },
+                "type": "banner"
+              }
+            }
+          }
+        ],
+        "seat": "appnexus"
+      }
+    ],
+    "bidid": "test bid id",
+    "cur": "USD",
+    "nbr": 0
+  },
+  "expectedReturnCode": 200
+}
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json
index 3d6a0774b9d..9565e41af1f 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict.json
@@ -37,10 +37,7 @@
     "regs": {
       "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN",
       "gpp_sid": [6],
-      "gdpr": 1,
-      "ext": {
-        "us_privacy": "1YYY"
-      }
+      "gdpr": 1
     },
     "user": {
       "consent": "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json
index 2102a8cd44b..54477ed0986 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/gdpr-conflict2.json
@@ -37,10 +37,7 @@
     "regs": {
       "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN",
       "gpp_sid": [2,6],
-      "gdpr": 1,
-      "ext": {
-        "us_privacy": "1YYY"
-      }
+      "gdpr": 1
     },
     "user": {
       "consent": "Invalid",
diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json
index 2ccdfb7ccdc..df2c426d0c3 100644
--- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json
+++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json
@@ -35,9 +35,7 @@
       }
     ],
     "regs": {
-      "ext": {
-        "us_privacy": "{invalid}"
-      }
+      "us_privacy": "{invalid}"
     },
     "user": {
       "ext": {}
diff --git a/endpoints/openrtb2/test_utils.go b/endpoints/openrtb2/test_utils.go
index 6fedb73cd47..329f7952369 100644
--- a/endpoints/openrtb2/test_utils.go
+++ b/endpoints/openrtb2/test_utils.go
@@ -34,6 +34,7 @@ import (
 	"github.com/prebid/prebid-server/v2/metrics"
 	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/ortb"
 	pbc "github.com/prebid/prebid-server/v2/prebid_cache_client"
 	"github.com/prebid/prebid-server/v2/stored_requests"
 	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
@@ -63,15 +64,16 @@ const (
 
 type testCase struct {
 	// Common
-	endpointType            int
-	Description             string            `json:"description"`
-	Config                  *testConfigValues `json:"config"`
-	BidRequest              json.RawMessage   `json:"mockBidRequest"`
-	ExpectedValidatedBidReq json.RawMessage   `json:"expectedValidatedBidRequest"`
-	ExpectedReturnCode      int               `json:"expectedReturnCode,omitempty"`
-	ExpectedErrorMessage    string            `json:"expectedErrorMessage"`
-	Query                   string            `json:"query"`
-	planBuilder             hooks.ExecutionPlanBuilder
+	endpointType               int
+	Description                string                     `json:"description"`
+	Config                     *testConfigValues          `json:"config"`
+	BidRequest                 json.RawMessage            `json:"mockBidRequest"`
+	ExpectedValidatedBidReq    json.RawMessage            `json:"expectedValidatedBidRequest"`
+	ExpectedMockBidderRequests map[string]json.RawMessage `json:"expectedMockBidderRequests"`
+	ExpectedReturnCode         int                        `json:"expectedReturnCode,omitempty"`
+	ExpectedErrorMessage       string                     `json:"expectedErrorMessage"`
+	Query                      string                     `json:"query"`
+	planBuilder                hooks.ExecutionPlanBuilder
 
 	// "/openrtb2/auction" endpoint JSON test info
 	ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"`
@@ -83,13 +85,20 @@ type testCase struct {
 }
 
 type testConfigValues struct {
-	AccountRequired     bool                          `json:"accountRequired"`
-	AliasJSON           string                        `json:"aliases"`
-	BlacklistedApps     []string                      `json:"blacklistedApps"`
-	DisabledAdapters    []string                      `json:"disabledAdapters"`
-	CurrencyRates       map[string]map[string]float64 `json:"currencyRates"`
-	MockBidders         []mockBidderHandler           `json:"mockBidders"`
-	RealParamsValidator bool                          `json:"realParamsValidator"`
+	AccountRequired     bool                           `json:"accountRequired"`
+	AliasJSON           string                         `json:"aliases"`
+	BlockedApps         []string                       `json:"blockedApps"`
+	DisabledAdapters    []string                       `json:"disabledAdapters"`
+	CurrencyRates       map[string]map[string]float64  `json:"currencyRates"`
+	MockBidders         []mockBidderHandler            `json:"mockBidders"`
+	RealParamsValidator bool                           `json:"realParamsValidator"`
+	BidderInfos         map[string]bidderInfoOverrides `json:"bidderInfoOverrides"`
+}
+type bidderInfoOverrides struct {
+	OpenRTB *OpenRTBInfo `json:"openrtb"`
+}
+type OpenRTBInfo struct {
+	Version string `json:"version"`
 }
 
 type brokenExchange struct{}
@@ -941,6 +950,7 @@ type mockBidderHandler struct {
 	Currency   string  `json:"currency"`
 	Price      float64 `json:"price"`
 	DealID     string  `json:"dealid"`
+	Seat       string  `json:"seat"`
 }
 
 func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
@@ -999,6 +1009,8 @@ func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
 type mockAdapter struct {
 	mockServerURL string
 	Server        config.Server
+	seat          string
+	requestData   [][]byte
 }
 
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
@@ -1009,7 +1021,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co
 	return adapter, nil
 }
 
-func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+func (a *mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	var requests []*adapters.RequestData
 	var errors []error
 
@@ -1029,11 +1041,12 @@ func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *ada
 			Body:   requestJSON,
 		}
 		requests = append(requests, requestData)
+		a.requestData = append(a.requestData, requestData.Body)
 	}
 	return requests, errors
 }
 
-func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+func (a *mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
 	if responseData.StatusCode != http.StatusOK {
 		switch responseData.StatusCode {
 		case http.StatusNoContent:
@@ -1064,6 +1077,9 @@ func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapter
 						Bid:     &seatBid.Bid[i],
 						BidType: openrtb_ext.BidTypeBanner,
 					}
+					if len(a.seat) > 0 {
+						b.Seat = openrtb_ext.BidderName(a.seat)
+					}
 					rv.Bids = append(rv.Bids, b)
 				}
 			}
@@ -1091,6 +1107,24 @@ func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.Bidder
 	return biddersInfos
 }
 
+func enableBidders(bidderInfos config.BidderInfos) {
+	for name, bidderInfo := range bidderInfos {
+		if bidderInfo.Disabled {
+			bidderInfo.Disabled = false
+			bidderInfos[name] = bidderInfo
+		}
+	}
+}
+
+func disableBidders(disabledAdapters []string, bidderInfos config.BidderInfos) {
+	for _, disabledAdapter := range disabledAdapters {
+		if bidderInfo, ok := bidderInfos[disabledAdapter]; ok {
+			bidderInfo.Disabled = true
+			bidderInfos[disabledAdapter] = bidderInfo
+		}
+	}
+}
+
 func newBidderInfo(isDisabled bool) config.BidderInfo {
 	return config.BidderInfo{
 		Disabled: isDisabled,
@@ -1135,27 +1169,40 @@ func parseTestData(fileData []byte, testFile string) (testCase, error) {
 		return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile)
 	}
 
+	// Get optional expected validated bid request
+	parsedTestData.ExpectedValidatedBidReq, _, _, err = jsonparser.Get(fileData, "expectedValidatedBidRequest")
+
+	// Get optional expected mock bidder requests
+	jsonExpectedMockBidderRequests, _, _, err := jsonparser.Get(fileData, "expectedMockBidderRequests")
+	if err == nil && jsonExpectedMockBidderRequests != nil {
+		parsedTestData.ExpectedMockBidderRequests = make(map[string]json.RawMessage)
+		if err = jsonutil.UnmarshalValid(jsonExpectedMockBidderRequests, &parsedTestData.ExpectedMockBidderRequests); err != nil {
+			return parsedTestData, fmt.Errorf("Error unmarshaling root.expectedMockBidderRequests from file %s. Desc: %v.", testFile, err)
+		}
+	}
+
 	parsedTestData.ExpectedReturnCode = int(parsedReturnCode)
 
 	return parsedTestData, nil
 }
 
-func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool {
-	var blacklistedAppMap map[string]bool
+func (tc *testConfigValues) getBlockedAppLookup() map[string]bool {
+	var blockedAppLookup map[string]bool
 
-	if len(tc.BlacklistedApps) > 0 {
-		blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps))
-		for _, app := range tc.BlacklistedApps {
-			blacklistedAppMap[app] = true
+	if len(tc.BlockedApps) > 0 {
+		blockedAppLookup = make(map[string]bool, len(tc.BlockedApps))
+		for _, app := range tc.BlockedApps {
+			blockedAppLookup[app] = true
 		}
 	}
-	return blacklistedAppMap
+	return blockedAppLookup
 }
 
 // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call
 type exchangeTestWrapper struct {
 	ex                    exchange.Exchange
 	actualValidatedBidReq *openrtb2.BidRequest
+	adapters              map[openrtb_ext.BidderName]exchange.AdaptedBidder
 }
 
 func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
@@ -1173,16 +1220,16 @@ func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.Auct
 }
 
 // buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server
-func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) {
+func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher, requestValidator ortb.RequestValidator) (exchange.Exchange, []*httptest.Server) {
 	if len(testCfg.MockBidders) == 0 {
 		testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00})
 	}
 	for _, mockBidder := range testCfg.MockBidders {
 		bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid))
-		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL}
+		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL, seat: mockBidder.Seat}
 		bidderName := openrtb_ext.BidderName(mockBidder.BidderName)
 
-		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "")
+		adapterMap[bidderName] = exchange.AdaptBidder(&bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "")
 		mockBidServersArray = append(mockBidServersArray, bidServer)
 	}
 
@@ -1194,8 +1241,10 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid
 	}.Builder
 
 	testExchange := exchange.NewExchange(adapterMap,
+
 		&wellBehavedCache{},
 		cfg,
+		requestValidator,
 		nil,
 		met,
 		bidderInfos,
@@ -1208,7 +1257,8 @@ func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.Bid
 	)
 
 	testExchange = &exchangeTestWrapper{
-		ex: testExchange,
+		ex:       testExchange,
+		adapters: adapterMap,
 	}
 
 	return testExchange, mockBidServersArray
@@ -1231,9 +1281,24 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 		paramValidator = mockBidderParamValidator{}
 	}
 
-	bidderInfos := getBidderInfos(test.Config.DisabledAdapters, openrtb_ext.CoreBidderNames())
+	bidderInfos, _ := config.LoadBidderInfoFromDisk("../../static/bidder-info")
+	for bidder, overrides := range test.Config.BidderInfos {
+		if bi, ok := bidderInfos[bidder]; ok {
+			if overrides.OpenRTB != nil && len(overrides.OpenRTB.Version) > 0 {
+				if bi.OpenRTB == nil {
+					bi.OpenRTB = &config.OpenRTBInfo{}
+				}
+				bi.OpenRTB.Version = overrides.OpenRTB.Version
+				bidderInfos[bidder] = bi
+			}
+		}
+	}
+
+	enableBidders(bidderInfos)
+	disableBidders(test.Config.DisabledAdapters, bidderInfos)
 	bidderMap := exchange.GetActiveBidders(bidderInfos)
 	disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos)
+	requestValidator := ortb.NewRequestValidator(bidderMap, disabledBidders, paramValidator)
 	met := &metricsConfig.NilMetricsEngine{}
 	mockFetcher := empty_fetcher.EmptyFetcher{}
 
@@ -1249,7 +1314,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 	}
 	mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle))
 
-	testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher)
+	testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher, requestValidator)
 
 	var storedRequestFetcher stored_requests.Fetcher
 	if len(test.StoredRequest) > 0 {
@@ -1267,8 +1332,9 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 
 	accountFetcher := &mockAccountFetcher{
 		data: map[string]json.RawMessage{
-			"malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`),
-			"disabled_acct":  json.RawMessage(`{"disabled":true}`),
+			"malformed_acct":             json.RawMessage(`{"disabled":"invalid type"}`),
+			"disabled_acct":              json.RawMessage(`{"disabled":true}`),
+			"alternate_bidder_code_acct": json.RawMessage(`{"disabled":false,"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}`),
 		},
 	}
 
@@ -1277,7 +1343,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 		planBuilder = hooks.EmptyPlanBuilder{}
 	}
 
-	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error)
+	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, ortb.RequestValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error)
 
 	switch test.endpointType {
 	case AMP_ENDPOINT:
@@ -1289,7 +1355,7 @@ func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Han
 	endpoint, err := endpointBuilder(
 		fakeUUIDGenerator{},
 		testExchange,
-		paramValidator,
+		requestValidator,
 		storedRequestFetcher,
 		accountFetcher,
 		cfg,
@@ -1400,10 +1466,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
+func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
 	return gdpr.AuctionPermissions{
 		AllowBidRequest: true,
-	}, nil
+	}
 }
 
 type mockPlanBuilder struct {
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 22248f1f36c..9625228fa82 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -46,7 +46,7 @@ var defaultRequestTimeout int64 = 5000
 func NewVideoEndpoint(
 	uuidGenerator uuidutil.UUIDGenerator,
 	ex exchange.Exchange,
-	validator openrtb_ext.BidderParamValidator,
+	requestValidator ortb.RequestValidator,
 	requestsById stored_requests.Fetcher,
 	videoFetcher stored_requests.Fetcher,
 	accounts stored_requests.AccountFetcher,
@@ -60,7 +60,7 @@ func NewVideoEndpoint(
 	tmaxAdjustments *exchange.TmaxAdjustmentsPreprocessed,
 ) (httprouter.Handle, error) {
 
-	if ex == nil || validator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
+	if ex == nil || requestValidator == nil || requestsById == nil || accounts == nil || cfg == nil || met == nil {
 		return nil, errors.New("NewVideoEndpoint requires non-nil arguments.")
 	}
 
@@ -76,7 +76,7 @@ func NewVideoEndpoint(
 	return httprouter.Handle((&endpointDeps{
 		uuidGenerator,
 		ex,
-		validator,
+		requestValidator,
 		requestsById,
 		videoFetcher,
 		accounts,
@@ -165,6 +165,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	}()
 
 	w.Header().Set("X-Prebid", version.BuildXPrebidHeader(version.Ver))
+	setBrowsingTopicsHeader(w, r)
 
 	lr := &io.LimitedReader{
 		R: r.Body,
@@ -259,16 +260,12 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 	// all code after this line should use the bidReqWrapper instead of bidReq directly
 	bidReqWrapper := &openrtb_ext.RequestWrapper{BidRequest: bidReq}
 
-	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
-	deps.setFieldsImplicitly(r, bidReqWrapper)
-
-	if err := ortb.SetDefaults(bidReqWrapper); err != nil {
-		handleError(&labels, w, errL, &vo, &debugLog)
+	if err := openrtb_ext.ConvertUpTo26(bidReqWrapper); err != nil {
+		handleError(&labels, w, []error{err}, &vo, &debugLog)
 		return
 	}
 
-	errL = deps.validateRequest(bidReqWrapper, false, false, nil, false)
-	if errortypes.ContainsFatalError(errL) {
+	if err := ortb.SetDefaults(bidReqWrapper); err != nil {
 		handleError(&labels, w, errL, &vo, &debugLog)
 		return
 	}
@@ -306,8 +303,22 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		return
 	}
 
+	// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
+	if errs := deps.setFieldsImplicitly(r, bidReqWrapper, account); len(errs) > 0 {
+		errL = append(errL, errs...)
+	}
+
+	errs := deps.validateRequest(account, r, bidReqWrapper, false, false, nil, false)
+	errL = append(errL, errs...)
+	if errortypes.ContainsFatalError(errL) {
+		handleError(&labels, w, errL, &vo, &debugLog)
+		return
+	}
+
 	activityControl = privacy.NewActivityControl(&account.Privacy)
 
+	warnings := errortypes.WarningOnly(errL)
+
 	secGPC := r.Header.Get("Sec-GPC")
 	auctionRequest := &exchange.AuctionRequest{
 		BidRequestWrapper:          bidReqWrapper,
@@ -316,6 +327,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		RequestType:                labels.RType,
 		StartTime:                  start,
 		LegacyLabels:               labels,
+		Warnings:                   warnings,
 		GlobalPrivacyControlHeader: secGPC,
 		PubID:                      labels.PubID,
 		HookExecutor:               hookexecution.EmptyHookExecutor{},
@@ -404,9 +416,9 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo
 	var status int = http.StatusInternalServerError
 	for _, er := range errL {
 		erVal := errortypes.ReadCode(er)
-		if erVal == errortypes.BlacklistedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode {
+		if erVal == errortypes.BlockedAppErrorCode || erVal == errortypes.AccountDisabledErrorCode {
 			status = http.StatusServiceUnavailable
-			labels.RequestStatus = metrics.RequestStatusBlacklisted
+			labels.RequestStatus = metrics.RequestStatusBlockedApp
 			break
 		} else if erVal == errortypes.AcctRequiredErrorCode {
 			status = http.StatusBadRequest
@@ -496,7 +508,7 @@ func createImpressionTemplate(imp openrtb2.Imp, video *openrtb2.Video) openrtb2.
 }
 
 func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []error) {
-	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond)
+	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deps.cfg.StoredRequestsTimeout)*time.Millisecond)
 	defer cancel()
 
 	impr := openrtb2.Imp{}
@@ -806,8 +818,8 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo)
 		errL = append(errL, err)
 	} else if req.App != nil {
 		if req.App.ID != "" {
-			if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found {
-				err := &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)}
+			if _, found := deps.cfg.BlockedAppsLookup[req.App.ID]; found {
+				err := &errortypes.BlockedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)}
 				errL = append(errL, err)
 				return errL, podErrors
 			}
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 98d6ca35c49..8670068cbea 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -813,15 +813,15 @@ func TestHandleError(t *testing.T) {
 				&errortypes.AccountDisabled{},
 			},
 			wantCode:          503,
-			wantMetricsStatus: metrics.RequestStatusBlacklisted,
+			wantMetricsStatus: metrics.RequestStatusBlockedApp,
 		},
 		{
 			description: "Blocked app - return 503 with blocked metrics status",
 			giveErrors: []error{
-				&errortypes.BlacklistedApp{},
+				&errortypes.BlockedApp{},
 			},
 			wantCode:          503,
-			wantMetricsStatus: metrics.RequestStatusBlacklisted,
+			wantMetricsStatus: metrics.RequestStatusBlockedApp,
 		},
 		{
 			description: "Account required error - return 400 with bad input metrics status",
@@ -1090,14 +1090,11 @@ func TestCCPA(t *testing.T) {
 		if ex.lastRequest == nil {
 			t.Fatalf("%s: The request never made it into the exchange.", test.description)
 		}
-		extRegs := &openrtb_ext.ExtRegs{}
-		if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil {
-			t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err)
-		}
+
 		if test.expectConsentString {
-			assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent")
+			assert.Len(t, ex.lastRequest.Regs.USPrivacy, 4, test.description+":consent")
 		} else if test.expectEmptyConsent {
-			assert.Empty(t, extRegs.USPrivacy, test.description+":consent")
+			assert.Empty(t, ex.lastRequest.Regs.USPrivacy, test.description+":consent")
 		}
 
 		// Validate HTTP Response
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index d576b8a0093..87208a09114 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -1701,12 +1701,12 @@ func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
+func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
 	return gdpr.AuctionPermissions{
 		AllowBidRequest: g.personalInfoAllowed,
 		PassGeo:         g.personalInfoAllowed,
 		PassID:          g.personalInfoAllowed,
-	}, nil
+	}
 }
 
 type fakeSyncer struct {
diff --git a/errortypes/code.go b/errortypes/code.go
index 399dd663498..357daa846d5 100644
--- a/errortypes/code.go
+++ b/errortypes/code.go
@@ -5,7 +5,7 @@ const (
 	UnknownErrorCode = 999
 	TimeoutErrorCode = iota
 	BadInputErrorCode
-	BlacklistedAppErrorCode
+	BlockedAppErrorCode
 	BadServerResponseErrorCode
 	FailedToRequestBidsErrorCode
 	BidderTemporarilyDisabledErrorCode
diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go
index d31c4166b06..1d33fa3c9d4 100644
--- a/errortypes/errortypes.go
+++ b/errortypes/errortypes.go
@@ -59,23 +59,20 @@ func (err *BadInput) Severity() Severity {
 	return SeverityFatal
 }
 
-// BlacklistedApp should be used when a request App.ID matches an entry in the BlacklistedApps
-// environment variable array
-//
-// These errors will be written to  http.ResponseWriter before canceling execution
-type BlacklistedApp struct {
+// BlockedApp should be used when a request App.ID matches an entry in the BlockedApp configuration.
+type BlockedApp struct {
 	Message string
 }
 
-func (err *BlacklistedApp) Error() string {
+func (err *BlockedApp) Error() string {
 	return err.Message
 }
 
-func (err *BlacklistedApp) Code() int {
-	return BlacklistedAppErrorCode
+func (err *BlockedApp) Code() int {
+	return BlockedAppErrorCode
 }
 
-func (err *BlacklistedApp) Severity() Severity {
+func (err *BlockedApp) Severity() Severity {
 	return SeverityFatal
 }
 
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index e188d2936e2..7f1496810e2 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -13,6 +13,7 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/adkernel"
 	"github.com/prebid/prebid-server/v2/adapters/adkernelAdn"
 	"github.com/prebid/prebid-server/v2/adapters/adman"
+	"github.com/prebid/prebid-server/v2/adapters/admatic"
 	"github.com/prebid/prebid-server/v2/adapters/admixer"
 	"github.com/prebid/prebid-server/v2/adapters/adnuntius"
 	"github.com/prebid/prebid-server/v2/adapters/adocean"
@@ -25,6 +26,7 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/adsinteractive"
 	"github.com/prebid/prebid-server/v2/adapters/adtarget"
 	"github.com/prebid/prebid-server/v2/adapters/adtelligent"
+	"github.com/prebid/prebid-server/v2/adapters/adtonos"
 	"github.com/prebid/prebid-server/v2/adapters/adtrgtme"
 	"github.com/prebid/prebid-server/v2/adapters/advangelists"
 	"github.com/prebid/prebid-server/v2/adapters/adview"
@@ -38,6 +40,7 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/apacdex"
 	"github.com/prebid/prebid-server/v2/adapters/appnexus"
 	"github.com/prebid/prebid-server/v2/adapters/appush"
+	"github.com/prebid/prebid-server/v2/adapters/aso"
 	"github.com/prebid/prebid-server/v2/adapters/audienceNetwork"
 	"github.com/prebid/prebid-server/v2/adapters/automatad"
 	"github.com/prebid/prebid-server/v2/adapters/avocet"
@@ -49,10 +52,12 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/between"
 	"github.com/prebid/prebid-server/v2/adapters/beyondmedia"
 	"github.com/prebid/prebid-server/v2/adapters/bidmachine"
+	"github.com/prebid/prebid-server/v2/adapters/bidmatic"
 	"github.com/prebid/prebid-server/v2/adapters/bidmyadz"
 	"github.com/prebid/prebid-server/v2/adapters/bidscube"
 	"github.com/prebid/prebid-server/v2/adapters/bidstack"
-	"github.com/prebid/prebid-server/v2/adapters/bizzclick"
+	"github.com/prebid/prebid-server/v2/adapters/bigoad"
+	"github.com/prebid/prebid-server/v2/adapters/blasto"
 	"github.com/prebid/prebid-server/v2/adapters/bliink"
 	"github.com/prebid/prebid-server/v2/adapters/blue"
 	"github.com/prebid/prebid-server/v2/adapters/bluesea"
@@ -62,12 +67,15 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/bwx"
 	cadentaperturemx "github.com/prebid/prebid-server/v2/adapters/cadent_aperture_mx"
 	"github.com/prebid/prebid-server/v2/adapters/ccx"
+	"github.com/prebid/prebid-server/v2/adapters/cointraffic"
 	"github.com/prebid/prebid-server/v2/adapters/coinzilla"
 	"github.com/prebid/prebid-server/v2/adapters/colossus"
 	"github.com/prebid/prebid-server/v2/adapters/compass"
+	"github.com/prebid/prebid-server/v2/adapters/concert"
 	"github.com/prebid/prebid-server/v2/adapters/connectad"
 	"github.com/prebid/prebid-server/v2/adapters/consumable"
 	"github.com/prebid/prebid-server/v2/adapters/conversant"
+	"github.com/prebid/prebid-server/v2/adapters/copper6ssp"
 	"github.com/prebid/prebid-server/v2/adapters/cpmstar"
 	"github.com/prebid/prebid-server/v2/adapters/criteo"
 	"github.com/prebid/prebid-server/v2/adapters/cwire"
@@ -76,13 +84,16 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/deepintent"
 	"github.com/prebid/prebid-server/v2/adapters/definemedia"
 	"github.com/prebid/prebid-server/v2/adapters/dianomi"
+	"github.com/prebid/prebid-server/v2/adapters/displayio"
 	"github.com/prebid/prebid-server/v2/adapters/dmx"
+	"github.com/prebid/prebid-server/v2/adapters/driftpixel"
 	"github.com/prebid/prebid-server/v2/adapters/dxkulture"
 	evolution "github.com/prebid/prebid-server/v2/adapters/e_volution"
 	"github.com/prebid/prebid-server/v2/adapters/edge226"
 	"github.com/prebid/prebid-server/v2/adapters/emtv"
 	"github.com/prebid/prebid-server/v2/adapters/eplanning"
 	"github.com/prebid/prebid-server/v2/adapters/epom"
+	"github.com/prebid/prebid-server/v2/adapters/escalax"
 	"github.com/prebid/prebid-server/v2/adapters/flipp"
 	"github.com/prebid/prebid-server/v2/adapters/freewheelssp"
 	"github.com/prebid/prebid-server/v2/adapters/frvradn"
@@ -110,20 +121,24 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/kiviads"
 	"github.com/prebid/prebid-server/v2/adapters/krushmedia"
 	"github.com/prebid/prebid-server/v2/adapters/lemmadigital"
-	"github.com/prebid/prebid-server/v2/adapters/liftoff"
 	"github.com/prebid/prebid-server/v2/adapters/limelightDigital"
 	lmkiviads "github.com/prebid/prebid-server/v2/adapters/lm_kiviads"
 	"github.com/prebid/prebid-server/v2/adapters/lockerdome"
 	"github.com/prebid/prebid-server/v2/adapters/logan"
 	"github.com/prebid/prebid-server/v2/adapters/logicad"
+	"github.com/prebid/prebid-server/v2/adapters/loyal"
 	"github.com/prebid/prebid-server/v2/adapters/lunamedia"
 	"github.com/prebid/prebid-server/v2/adapters/mabidder"
 	"github.com/prebid/prebid-server/v2/adapters/madvertise"
 	"github.com/prebid/prebid-server/v2/adapters/marsmedia"
+	"github.com/prebid/prebid-server/v2/adapters/mediago"
 	"github.com/prebid/prebid-server/v2/adapters/medianet"
+	"github.com/prebid/prebid-server/v2/adapters/melozen"
+	"github.com/prebid/prebid-server/v2/adapters/metax"
 	"github.com/prebid/prebid-server/v2/adapters/mgid"
 	"github.com/prebid/prebid-server/v2/adapters/mgidX"
 	"github.com/prebid/prebid-server/v2/adapters/minutemedia"
+	"github.com/prebid/prebid-server/v2/adapters/missena"
 	"github.com/prebid/prebid-server/v2/adapters/mobfoxpb"
 	"github.com/prebid/prebid-server/v2/adapters/mobilefuse"
 	"github.com/prebid/prebid-server/v2/adapters/motorik"
@@ -134,19 +149,25 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/openweb"
 	"github.com/prebid/prebid-server/v2/adapters/openx"
 	"github.com/prebid/prebid-server/v2/adapters/operaads"
+	"github.com/prebid/prebid-server/v2/adapters/oraki"
 	"github.com/prebid/prebid-server/v2/adapters/orbidder"
 	"github.com/prebid/prebid-server/v2/adapters/outbrain"
 	"github.com/prebid/prebid-server/v2/adapters/ownadx"
 	"github.com/prebid/prebid-server/v2/adapters/pangle"
 	"github.com/prebid/prebid-server/v2/adapters/pgamssp"
+	"github.com/prebid/prebid-server/v2/adapters/playdigo"
 	"github.com/prebid/prebid-server/v2/adapters/pubmatic"
 	"github.com/prebid/prebid-server/v2/adapters/pubnative"
+	"github.com/prebid/prebid-server/v2/adapters/pubrise"
 	"github.com/prebid/prebid-server/v2/adapters/pulsepoint"
 	"github.com/prebid/prebid-server/v2/adapters/pwbid"
+	"github.com/prebid/prebid-server/v2/adapters/qt"
+	"github.com/prebid/prebid-server/v2/adapters/readpeak"
 	"github.com/prebid/prebid-server/v2/adapters/relevantdigital"
 	"github.com/prebid/prebid-server/v2/adapters/revcontent"
 	"github.com/prebid/prebid-server/v2/adapters/richaudience"
 	"github.com/prebid/prebid-server/v2/adapters/rise"
+	"github.com/prebid/prebid-server/v2/adapters/roulax"
 	"github.com/prebid/prebid-server/v2/adapters/rtbhouse"
 	"github.com/prebid/prebid-server/v2/adapters/rubicon"
 	salunamedia "github.com/prebid/prebid-server/v2/adapters/sa_lunamedia"
@@ -162,6 +183,7 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/smartx"
 	"github.com/prebid/prebid-server/v2/adapters/smartyads"
 	"github.com/prebid/prebid-server/v2/adapters/smilewanted"
+	"github.com/prebid/prebid-server/v2/adapters/smrtconnect"
 	"github.com/prebid/prebid-server/v2/adapters/sonobi"
 	"github.com/prebid/prebid-server/v2/adapters/sovrn"
 	"github.com/prebid/prebid-server/v2/adapters/sovrnXsp"
@@ -171,14 +193,18 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/tappx"
 	"github.com/prebid/prebid-server/v2/adapters/teads"
 	"github.com/prebid/prebid-server/v2/adapters/telaria"
+	"github.com/prebid/prebid-server/v2/adapters/theadx"
+	"github.com/prebid/prebid-server/v2/adapters/thetradedesk"
 	"github.com/prebid/prebid-server/v2/adapters/tpmn"
 	"github.com/prebid/prebid-server/v2/adapters/trafficgate"
 	"github.com/prebid/prebid-server/v2/adapters/triplelift"
 	"github.com/prebid/prebid-server/v2/adapters/triplelift_native"
+	"github.com/prebid/prebid-server/v2/adapters/trustedstack"
 	"github.com/prebid/prebid-server/v2/adapters/ucfunnel"
 	"github.com/prebid/prebid-server/v2/adapters/undertone"
 	"github.com/prebid/prebid-server/v2/adapters/unicorn"
 	"github.com/prebid/prebid-server/v2/adapters/unruly"
+	"github.com/prebid/prebid-server/v2/adapters/vidazoo"
 	"github.com/prebid/prebid-server/v2/adapters/videobyte"
 	"github.com/prebid/prebid-server/v2/adapters/videoheroes"
 	"github.com/prebid/prebid-server/v2/adapters/vidoomy"
@@ -186,6 +212,7 @@ import (
 	"github.com/prebid/prebid-server/v2/adapters/visx"
 	"github.com/prebid/prebid-server/v2/adapters/vox"
 	"github.com/prebid/prebid-server/v2/adapters/vrtcal"
+	"github.com/prebid/prebid-server/v2/adapters/vungle"
 	"github.com/prebid/prebid-server/v2/adapters/xeworks"
 	"github.com/prebid/prebid-server/v2/adapters/yahooAds"
 	"github.com/prebid/prebid-server/v2/adapters/yandex"
@@ -215,6 +242,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAdkernel:          adkernel.Builder,
 		openrtb_ext.BidderAdkernelAdn:       adkernelAdn.Builder,
 		openrtb_ext.BidderAdman:             adman.Builder,
+		openrtb_ext.BidderAdmatic:           admatic.Builder,
 		openrtb_ext.BidderAdmixer:           admixer.Builder,
 		openrtb_ext.BidderAdnuntius:         adnuntius.Builder,
 		openrtb_ext.BidderAdOcean:           adocean.Builder,
@@ -228,6 +256,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderAdtarget:          adtarget.Builder,
 		openrtb_ext.BidderAdtrgtme:          adtrgtme.Builder,
 		openrtb_ext.BidderAdtelligent:       adtelligent.Builder,
+		openrtb_ext.BidderAdTonos:           adtonos.Builder,
 		openrtb_ext.BidderAdvangelists:      advangelists.Builder,
 		openrtb_ext.BidderAdView:            adview.Builder,
 		openrtb_ext.BidderAdxcg:             adxcg.Builder,
@@ -240,6 +269,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderApacdex:           apacdex.Builder,
 		openrtb_ext.BidderAppnexus:          appnexus.Builder,
 		openrtb_ext.BidderAppush:            appush.Builder,
+		openrtb_ext.BidderAso:               aso.Builder,
 		openrtb_ext.BidderAudienceNetwork:   audienceNetwork.Builder,
 		openrtb_ext.BidderAutomatad:         automatad.Builder,
 		openrtb_ext.BidderAvocet:            avocet.Builder,
@@ -251,10 +281,12 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBetween:           between.Builder,
 		openrtb_ext.BidderBeyondMedia:       beyondmedia.Builder,
 		openrtb_ext.BidderBidmachine:        bidmachine.Builder,
+		openrtb_ext.BidderBidmatic:          bidmatic.Builder,
 		openrtb_ext.BidderBidmyadz:          bidmyadz.Builder,
 		openrtb_ext.BidderBidsCube:          bidscube.Builder,
 		openrtb_ext.BidderBidstack:          bidstack.Builder,
-		openrtb_ext.BidderBizzclick:         bizzclick.Builder,
+		openrtb_ext.BidderBigoAd:            bigoad.Builder,
+		openrtb_ext.BidderBlasto:            blasto.Builder,
 		openrtb_ext.BidderBliink:            bliink.Builder,
 		openrtb_ext.BidderBlue:              blue.Builder,
 		openrtb_ext.BidderBluesea:           bluesea.Builder,
@@ -264,12 +296,15 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderBWX:               bwx.Builder,
 		openrtb_ext.BidderCadentApertureMX:  cadentaperturemx.Builder,
 		openrtb_ext.BidderCcx:               ccx.Builder,
+		openrtb_ext.BidderCointraffic:       cointraffic.Builder,
 		openrtb_ext.BidderCoinzilla:         coinzilla.Builder,
 		openrtb_ext.BidderColossus:          colossus.Builder,
 		openrtb_ext.BidderCompass:           compass.Builder,
+		openrtb_ext.BidderConcert:           concert.Builder,
 		openrtb_ext.BidderConnectAd:         connectad.Builder,
 		openrtb_ext.BidderConsumable:        consumable.Builder,
 		openrtb_ext.BidderConversant:        conversant.Builder,
+		openrtb_ext.BidderCopper6ssp:        copper6ssp.Builder,
 		openrtb_ext.BidderCpmstar:           cpmstar.Builder,
 		openrtb_ext.BidderCriteo:            criteo.Builder,
 		openrtb_ext.BidderCWire:             cwire.Builder,
@@ -278,13 +313,16 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderDeepintent:        deepintent.Builder,
 		openrtb_ext.BidderDefinemedia:       definemedia.Builder,
 		openrtb_ext.BidderDianomi:           dianomi.Builder,
+		openrtb_ext.BidderDisplayio:         displayio.Builder,
 		openrtb_ext.BidderEdge226:           edge226.Builder,
 		openrtb_ext.BidderDmx:               dmx.Builder,
 		openrtb_ext.BidderDXKulture:         dxkulture.Builder,
+		openrtb_ext.BidderDriftPixel:        driftpixel.Builder,
 		openrtb_ext.BidderEmtv:              emtv.Builder,
 		openrtb_ext.BidderEmxDigital:        cadentaperturemx.Builder,
 		openrtb_ext.BidderEPlanning:         eplanning.Builder,
 		openrtb_ext.BidderEpom:              epom.Builder,
+		openrtb_ext.BidderEscalax:           escalax.Builder,
 		openrtb_ext.BidderEVolution:         evolution.Builder,
 		openrtb_ext.BidderFlipp:             flipp.Builder,
 		openrtb_ext.BidderFreewheelSSP:      freewheelssp.Builder,
@@ -314,20 +352,25 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderLmKiviads:         lmkiviads.Builder,
 		openrtb_ext.BidderKrushmedia:        krushmedia.Builder,
 		openrtb_ext.BidderLemmadigital:      lemmadigital.Builder,
-		openrtb_ext.BidderLiftoff:           liftoff.Builder,
+		openrtb_ext.BidderVungle:            vungle.Builder,
 		openrtb_ext.BidderLimelightDigital:  limelightDigital.Builder,
 		openrtb_ext.BidderLockerDome:        lockerdome.Builder,
 		openrtb_ext.BidderLogan:             logan.Builder,
 		openrtb_ext.BidderLogicad:           logicad.Builder,
+		openrtb_ext.BidderLoyal:             loyal.Builder,
 		openrtb_ext.BidderLunaMedia:         lunamedia.Builder,
 		openrtb_ext.BidderMabidder:          mabidder.Builder,
 		openrtb_ext.BidderMadvertise:        madvertise.Builder,
 		openrtb_ext.BidderMarsmedia:         marsmedia.Builder,
 		openrtb_ext.BidderMediafuse:         appnexus.Builder,
+		openrtb_ext.BidderMediaGo:           mediago.Builder,
 		openrtb_ext.BidderMedianet:          medianet.Builder,
+		openrtb_ext.BidderMeloZen:           melozen.Builder,
+		openrtb_ext.BidderMetaX:             metax.Builder,
 		openrtb_ext.BidderMgid:              mgid.Builder,
 		openrtb_ext.BidderMgidX:             mgidX.Builder,
 		openrtb_ext.BidderMinuteMedia:       minutemedia.Builder,
+		openrtb_ext.BidderMissena:           missena.Builder,
 		openrtb_ext.BidderMobfoxpb:          mobfoxpb.Builder,
 		openrtb_ext.BidderMobileFuse:        mobilefuse.Builder,
 		openrtb_ext.BidderMotorik:           motorik.Builder,
@@ -338,19 +381,25 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderOpenWeb:           openweb.Builder,
 		openrtb_ext.BidderOpenx:             openx.Builder,
 		openrtb_ext.BidderOperaads:          operaads.Builder,
+		openrtb_ext.BidderOraki:             oraki.Builder,
 		openrtb_ext.BidderOrbidder:          orbidder.Builder,
 		openrtb_ext.BidderOutbrain:          outbrain.Builder,
 		openrtb_ext.BidderOwnAdx:            ownadx.Builder,
 		openrtb_ext.BidderPangle:            pangle.Builder,
 		openrtb_ext.BidderPGAMSsp:           pgamssp.Builder,
+		openrtb_ext.BidderPlaydigo:          playdigo.Builder,
 		openrtb_ext.BidderPubmatic:          pubmatic.Builder,
 		openrtb_ext.BidderPubnative:         pubnative.Builder,
+		openrtb_ext.BidderPubrise:           pubrise.Builder,
 		openrtb_ext.BidderPulsepoint:        pulsepoint.Builder,
 		openrtb_ext.BidderPWBid:             pwbid.Builder,
+		openrtb_ext.BidderQT:                qt.Builder,
+		openrtb_ext.BidderReadpeak:          readpeak.Builder,
 		openrtb_ext.BidderRelevantDigital:   relevantdigital.Builder,
 		openrtb_ext.BidderRevcontent:        revcontent.Builder,
 		openrtb_ext.BidderRichaudience:      richaudience.Builder,
 		openrtb_ext.BidderRise:              rise.Builder,
+		openrtb_ext.BidderRoulax:            roulax.Builder,
 		openrtb_ext.BidderRTBHouse:          rtbhouse.Builder,
 		openrtb_ext.BidderRubicon:           rubicon.Builder,
 		openrtb_ext.BidderSeedingAlliance:   seedingAlliance.Builder,
@@ -366,6 +415,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderSmartx:            smartx.Builder,
 		openrtb_ext.BidderSmartyAds:         smartyads.Builder,
 		openrtb_ext.BidderSmileWanted:       smilewanted.Builder,
+		openrtb_ext.BidderSmrtconnect:       smrtconnect.Builder,
 		openrtb_ext.BidderSonobi:            sonobi.Builder,
 		openrtb_ext.BidderSovrn:             sovrn.Builder,
 		openrtb_ext.BidderSovrnXsp:          sovrnXsp.Builder,
@@ -375,14 +425,18 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderTappx:             tappx.Builder,
 		openrtb_ext.BidderTeads:             teads.Builder,
 		openrtb_ext.BidderTelaria:           telaria.Builder,
+		openrtb_ext.BidderTheadx:            theadx.Builder,
+		openrtb_ext.BidderTheTradeDesk:      thetradedesk.Builder,
 		openrtb_ext.BidderTpmn:              tpmn.Builder,
 		openrtb_ext.BidderTrafficGate:       trafficgate.Builder,
 		openrtb_ext.BidderTriplelift:        triplelift.Builder,
 		openrtb_ext.BidderTripleliftNative:  triplelift_native.Builder,
+		openrtb_ext.BidderTrustedstack:      trustedstack.Builder,
 		openrtb_ext.BidderUcfunnel:          ucfunnel.Builder,
 		openrtb_ext.BidderUndertone:         undertone.Builder,
 		openrtb_ext.BidderUnicorn:           unicorn.Builder,
 		openrtb_ext.BidderUnruly:            unruly.Builder,
+		openrtb_ext.BidderVidazoo:           vidazoo.Builder,
 		openrtb_ext.BidderVideoByte:         videobyte.Builder,
 		openrtb_ext.BidderVideoHeroes:       videoheroes.Builder,
 		openrtb_ext.BidderVidoomy:           vidoomy.Builder,
diff --git a/exchange/bidder.go b/exchange/bidder.go
index 8b54f9847bb..59f4623b252 100644
--- a/exchange/bidder.go
+++ b/exchange/bidder.go
@@ -76,12 +76,14 @@ type bidRequestOptions struct {
 
 type extraBidderRespInfo struct {
 	respProcessingStartTime time.Time
+	seatNonBidBuilder       SeatNonBidBuilder
 }
 
 type extraAuctionResponseInfo struct {
 	fledge                  *openrtb_ext.Fledge
 	bidsFound               bool
 	bidderResponseStartTime time.Time
+	seatNonBidBuilder       SeatNonBidBuilder
 }
 
 const ImpIdReqBody = "Stored bid response for impression id: "
@@ -96,7 +98,7 @@ const (
 // The name refers to the "Adapter" architecture pattern, and should not be confused with a Prebid "Adapter"
 // (which is being phased out and replaced by Bidder for OpenRTB auctions)
 func AdaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo, endpointCompression string) AdaptedBidder {
-	return &bidderAdapter{
+	return &BidderAdapter{
 		Bidder:     bidder,
 		BidderName: name,
 		Client:     client,
@@ -117,7 +119,7 @@ func parseDebugInfo(info *config.DebugInfo) bool {
 	return info.Allow
 }
 
-type bidderAdapter struct {
+type BidderAdapter struct {
 	Bidder     adapters.Bidder
 	BidderName openrtb_ext.BidderName
 	Client     *http.Client
@@ -132,9 +134,10 @@ type bidderAdapterConfig struct {
 	EndpointCompression string
 }
 
-func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) {
+func (bidder *BidderAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, hookExecutor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) {
 	request := openrtb_ext.RequestWrapper{BidRequest: bidderRequest.BidRequest}
 	reject := hookExecutor.ExecuteBidderRequestStage(&request, string(bidderRequest.BidderName))
+	seatNonBidBuilder := SeatNonBidBuilder{}
 	if reject != nil {
 		return nil, extraBidderRespInfo{}, []error{reject}
 	}
@@ -397,13 +400,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, bidderRequest Bidde
 			}
 		} else {
 			errs = append(errs, httpInfo.err)
+			nonBidReason := httpInfoToNonBidReason(httpInfo)
+			seatNonBidBuilder.rejectImps(httpInfo.request.ImpIDs, nonBidReason, string(bidderRequest.BidderName))
 		}
 	}
+
 	seatBids := make([]*entities.PbsOrtbSeatBid, 0, len(seatBidMap))
 	for _, seatBid := range seatBidMap {
 		seatBids = append(seatBids, seatBid)
 	}
 
+	extraRespInfo.seatNonBidBuilder = seatNonBidBuilder
 	return seatBids, extraRespInfo, errs
 }
 
@@ -518,11 +525,11 @@ func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall {
 
 // doRequest makes a request, handles the response, and returns the data needed by the
 // Bidder interface.
-func (bidder *bidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
+func (bidder *BidderAdapter) doRequest(ctx context.Context, req *adapters.RequestData, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
 	return bidder.doRequestImpl(ctx, req, glog.Warningf, bidderRequestStartTime, tmaxAdjustments)
 }
 
-func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
+func (bidder *BidderAdapter) doRequestImpl(ctx context.Context, req *adapters.RequestData, logger util.LogMsg, bidderRequestStartTime time.Time, tmaxAdjustments *TmaxAdjustmentsPreprocessed) *httpCallInfo {
 	requestBody, err := getRequestBody(req, bidder.config.EndpointCompression)
 	if err != nil {
 		return &httpCallInfo{
@@ -609,7 +616,7 @@ func (bidder *bidderAdapter) doRequestImpl(ctx context.Context, req *adapters.Re
 	}
 }
 
-func (bidder *bidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) {
+func (bidder *BidderAdapter) doTimeoutNotification(timeoutBidder adapters.TimeoutBidder, req *adapters.RequestData, logger util.LogMsg) {
 	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
 	defer cancel()
 	toReq, errL := timeoutBidder.MakeTimeoutNotification(req)
@@ -659,7 +666,7 @@ type httpCallInfo struct {
 // This function adds an httptrace.ClientTrace object to the context so, if connection with the bidder
 // endpoint is established, we can keep track of whether the connection was newly created, reused, and
 // the time from the connection request, to the connection creation.
-func (bidder *bidderAdapter) addClientTrace(ctx context.Context) context.Context {
+func (bidder *BidderAdapter) addClientTrace(ctx context.Context) context.Context {
 	var connStart, dnsStart, tlsStart time.Time
 
 	trace := &httptrace.ClientTrace{
diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go
index 9f092b7dea3..dfe58b0bed1 100644
--- a/exchange/bidder_test.go
+++ b/exchange/bidder_test.go
@@ -9,11 +9,15 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"net/http/httptrace"
+	"net/url"
+	"os"
 	"sort"
 	"strings"
+	"syscall"
 	"testing"
 	"time"
 
@@ -552,7 +556,7 @@ func TestBidderTimeout(t *testing.T) {
 	server := httptest.NewServer(handler)
 	defer server.Close()
 
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder:     &mixedMultiBidder{},
 		BidderName: openrtb_ext.BidderAppnexus,
 		Client:     server.Client(),
@@ -574,7 +578,7 @@ func TestBidderTimeout(t *testing.T) {
 // TestInvalidRequest makes sure that bidderAdapter.doRequest returns errors on bad requests.
 func TestInvalidRequest(t *testing.T) {
 	server := httptest.NewServer(mockHandler(200, "getBody", "postBody"))
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder: &mixedMultiBidder{},
 		Client: server.Client(),
 	}
@@ -595,7 +599,7 @@ func TestConnectionClose(t *testing.T) {
 	})
 	server = httptest.NewServer(handler)
 
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder:     &mixedMultiBidder{},
 		Client:     server.Client(),
 		BidderName: openrtb_ext.BidderAppnexus,
@@ -2145,7 +2149,7 @@ func TestCallRecordDNSTime(t *testing.T) {
 	// Instantiate the bidder that will send the request. We'll make sure to use an
 	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
 	// gets called
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder: &mixedMultiBidder{},
 		Client: &http.Client{Transport: DNSDoneTripper{}},
 		me:     metricsMock,
@@ -2169,7 +2173,7 @@ func TestCallRecordTLSHandshakeTime(t *testing.T) {
 	// Instantiate the bidder that will send the request. We'll make sure to use an
 	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
 	// gets called
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder: &mixedMultiBidder{},
 		Client: &http.Client{Transport: TLSHandshakeTripper{}},
 		me:     metricsMock,
@@ -2197,7 +2201,7 @@ func TestTimeoutNotificationOff(t *testing.T) {
 			Headers: http.Header{},
 		},
 	}
-	bidder := &bidderAdapter{
+	bidder := &BidderAdapter{
 		Bidder: bidderImpl,
 		Client: server.Client(),
 		config: bidderAdapterConfig{Debug: config.Debug{}},
@@ -2231,7 +2235,7 @@ func TestTimeoutNotificationOn(t *testing.T) {
 	// Wrap with BidderInfo to mimic exchange.go flow.
 	bidderWrappedWithInfo := wrapWithBidderInfo(bidder)
 
-	bidderAdapter := &bidderAdapter{
+	bidderAdapter := &BidderAdapter{
 		Bidder: bidderWrappedWithInfo,
 		Client: server.Client(),
 		config: bidderAdapterConfig{
@@ -3099,6 +3103,148 @@ func TestGetBidType(t *testing.T) {
 	}
 }
 
+func TestSeatNonBid(t *testing.T) {
+	type args struct {
+		BidRequest     *openrtb2.BidRequest
+		Seat           string
+		SeatRequests   []*adapters.RequestData
+		BidderResponse func() (*http.Response, error)
+		client         *http.Client
+	}
+	type expect struct {
+		seatBids    []*entities.PbsOrtbSeatBid
+		seatNonBids SeatNonBidBuilder
+		errors      []error
+	}
+	testCases := []struct {
+		name   string
+		args   args
+		expect expect
+	}{
+		{
+			name: "NBR_101_timeout_for_context_deadline_exceeded",
+			args: args{
+				Seat: "pubmatic",
+				BidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{{ID: "1234"}},
+				},
+				SeatRequests:   []*adapters.RequestData{{ImpIDs: []string{"1234"}}},
+				BidderResponse: func() (*http.Response, error) { return nil, context.DeadlineExceeded },
+				client:         &http.Client{Timeout: time.Nanosecond}, // for timeout
+			},
+			expect: expect{
+				seatNonBids: SeatNonBidBuilder{
+					"pubmatic": {{
+						ImpId:      "1234",
+						StatusCode: int(ErrorTimeout),
+					}},
+				},
+				errors:   []error{&errortypes.Timeout{Message: context.DeadlineExceeded.Error()}},
+				seatBids: []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", Seat: "pubmatic", HttpCalls: []*openrtb_ext.ExtHttpCall{}}},
+			},
+		}, {
+			name: "NBR_103_Bidder_Unreachable_Connection_Refused",
+			args: args{
+				Seat:         "appnexus",
+				SeatRequests: []*adapters.RequestData{{ImpIDs: []string{"1234", "4567"}}},
+				BidRequest:   &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1234"}, {ID: "4567"}}},
+				BidderResponse: func() (*http.Response, error) {
+					return nil, &net.OpError{Err: os.NewSyscallError(syscall.ECONNREFUSED.Error(), syscall.ECONNREFUSED)}
+				},
+			},
+			expect: expect{
+				seatNonBids: SeatNonBidBuilder{
+					"appnexus": {
+						{ImpId: "1234", StatusCode: int(ErrorBidderUnreachable)},
+						{ImpId: "4567", StatusCode: int(ErrorBidderUnreachable)},
+					},
+				},
+				seatBids: []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", Seat: "appnexus", HttpCalls: []*openrtb_ext.ExtHttpCall{}}},
+				errors:   []error{&url.Error{Op: "Get", URL: "", Err: &net.OpError{Err: os.NewSyscallError(syscall.ECONNREFUSED.Error(), syscall.ECONNREFUSED)}}},
+			},
+		}, {
+			name: "no_impids_populated_in_request_data",
+			args: args{
+				SeatRequests: []*adapters.RequestData{{
+					ImpIDs: nil, // no imp ids
+				}},
+				BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1234"}}},
+				BidderResponse: func() (*http.Response, error) {
+					return nil, errors.New("some_error")
+				},
+			},
+			expect: expect{
+				seatNonBids: SeatNonBidBuilder{},
+				seatBids:    []*entities.PbsOrtbSeatBid{{Bids: []*entities.PbsOrtbBid{}, Currency: "USD", HttpCalls: []*openrtb_ext.ExtHttpCall{}}},
+				errors:      []error{&url.Error{Op: "Get", URL: "", Err: errors.New("some_error")}},
+			},
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			mockBidder := &mockBidder{}
+			mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return(test.args.SeatRequests, []error(nil))
+			mockMetricsEngine := &metrics.MetricsEngineMock{}
+			mockMetricsEngine.On("RecordOverheadTime", mock.Anything, mock.Anything).Return(nil)
+			mockMetricsEngine.On("RecordBidderServerResponseTime", mock.Anything).Return(nil)
+			roundTrip := &mockRoundTripper{}
+			roundTrip.On("RoundTrip", mock.Anything).Return(test.args.BidderResponse())
+			client := &http.Client{
+				Transport: roundTrip,
+				Timeout:   0,
+			}
+			if test.args.client != nil {
+				client.Timeout = test.args.client.Timeout
+			}
+			bidder := AdaptBidder(mockBidder, client, &config.Configuration{}, mockMetricsEngine, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, test.args.Seat)
+
+			ctx := context.Background()
+			if client.Timeout > 0 {
+				ctxTimeout, cancel := context.WithTimeout(ctx, client.Timeout)
+				ctx = ctxTimeout
+				defer cancel()
+			}
+			seatBids, responseExtra, errors := bidder.requestBid(ctx, BidderRequest{
+				BidRequest: test.args.BidRequest,
+				BidderName: openrtb_ext.BidderName(test.args.Seat),
+			}, nil, &adapters.ExtraRequestInfo{}, &MockSigner{}, bidRequestOptions{}, openrtb_ext.ExtAlternateBidderCodes{}, hookexecution.EmptyHookExecutor{}, nil)
+			assert.Equal(t, test.expect.seatBids, seatBids)
+			assert.Equal(t, test.expect.seatNonBids, responseExtra.seatNonBidBuilder)
+			assert.Equal(t, test.expect.errors, errors)
+			for _, nonBids := range responseExtra.seatNonBidBuilder {
+				for _, nonBid := range nonBids {
+					for _, seatBid := range seatBids {
+						for _, bid := range seatBid.Bids {
+							// ensure non bids are not present in seat bids
+							if nonBid.ImpId == bid.Bid.ImpID {
+								assert.Fail(t, "imp id [%s] present in both seat bid and non seat bid", nonBid.ImpId)
+							}
+						}
+					}
+				}
+			}
+		})
+	}
+}
+
+type mockRoundTripper struct {
+	mock.Mock
+}
+
+func (rt *mockRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
+	args := rt.Called(request)
+	var response *http.Response
+	if args.Get(0) != nil {
+		response = args.Get(0).(*http.Response)
+	}
+	var err error
+	if args.Get(1) != nil {
+		err = args.Get(1).(error)
+	}
+
+	return response, err
+}
+
 type mockBidderTmaxCtx struct {
 	startTime, deadline, now time.Time
 	ok                       bool
@@ -3238,7 +3384,7 @@ func TestDoRequestImplWithTmax(t *testing.T) {
 		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
 	}
 
-	bidderAdapter := bidderAdapter{
+	bidderAdapter := BidderAdapter{
 		me:     &metricsConfig.NilMetricsEngine{},
 		Client: server.Client(),
 	}
@@ -3313,7 +3459,7 @@ func TestDoRequestImplWithTmaxTimeout(t *testing.T) {
 	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
 	metricsMock.On("RecordTMaxTimeout").Once()
 
-	bidderAdapter := bidderAdapter{
+	bidderAdapter := BidderAdapter{
 		me:     metricsMock,
 		Client: server.Client(),
 	}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index b9dae6725c6..a8787ac3db2 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -14,6 +14,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/prebid/prebid-server/v2/ortb"
 	"github.com/prebid/prebid-server/v2/privacy"
 
 	"github.com/prebid/prebid-server/v2/adapters"
@@ -94,6 +95,8 @@ type seatResponseExtra struct {
 	// httpCalls is the list of debugging info. It should only be populated if the request.test == 1.
 	// This will become response.ext.debug.httpcalls.{bidder} on the final Response.
 	HttpCalls []*openrtb_ext.ExtHttpCall
+	// NonBid contains non bid reason information
+	NonBid *openrtb_ext.NonBid
 }
 
 type bidResponseWrapper struct {
@@ -102,10 +105,11 @@ type bidResponseWrapper struct {
 	bidder                  openrtb_ext.BidderName
 	adapter                 openrtb_ext.BidderName
 	bidderResponseStartTime time.Time
+	seatNonBidBuilder       SeatNonBidBuilder
 }
 
 type BidIDGenerator interface {
-	New() (string, error)
+	New(bidder string) (string, error)
 	Enabled() bool
 }
 
@@ -117,7 +121,7 @@ func (big *bidIDGenerator) Enabled() bool {
 	return big.enabled
 }
 
-func (big *bidIDGenerator) New() (string, error) {
+func (big *bidIDGenerator) New(bidder string) (string, error) {
 	rawUuid, err := uuid.NewV4()
 	return rawUuid.String(), err
 }
@@ -132,7 +136,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, macroReplacer macros.Replacer, priceFloorFetcher floors.FloorFetcher) Exchange {
+func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, requestValidator ortb.RequestValidator, 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, priceFloorFetcher floors.FloorFetcher) Exchange {
 	bidderToSyncerKey := map[string]string{}
 	for bidder, syncer := range syncersByBidder {
 		bidderToSyncerKey[bidder] = syncer.Key()
@@ -155,6 +159,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]AdaptedBidder, cache prebid
 		gdprPermsBuilder:  gdprPermsBuilder,
 		hostSChainNode:    cfg.HostSChainNode,
 		bidderInfo:        infos,
+		requestValidator:  requestValidator,
 	}
 
 	return &exchange{
@@ -313,6 +318,19 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 
 	// Make our best guess if GDPR applies
 	gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequestWrapper)
+	gdprSignal, err := getGDPR(r.BidRequestWrapper)
+	if err != nil {
+		return nil, err
+	}
+	channelEnabled := r.TCF2Config.ChannelEnabled(channelTypeMap[r.LegacyLabels.RType])
+	gdprEnforced := enforceGDPR(gdprSignal, gdprDefaultValue, channelEnabled)
+	dsaWriter := dsa.Writer{
+		Config:      r.Account.Privacy.DSA,
+		GDPRInScope: gdprEnforced,
+	}
+	if err := dsaWriter.Write(r.BidRequestWrapper); err != nil {
+		return nil, err
+	}
 
 	// rebuild/resync the request in the request wrapper.
 	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
@@ -324,7 +342,12 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		Prebid: *requestExtPrebid,
 		SChain: requestExt.GetSChain(),
 	}
-	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprDefaultValue, bidAdjustmentFactors)
+	bidderRequests, privacyLabels, errs := e.requestSplitter.cleanOpenRTBRequests(ctx, *r, requestExtLegacy, gdprSignal, gdprEnforced, bidAdjustmentFactors)
+	for _, err := range errs {
+		if errortypes.ReadCode(err) == errortypes.InvalidImpFirstPartyDataErrorCode {
+			return nil, err
+		}
+	}
 	errs = append(errs, floorErrs...)
 
 	mergedBidAdj, err := bidadjustment.Merge(r.BidRequestWrapper, r.Account.BidAdjustments)
@@ -353,7 +376,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		fledge          *openrtb_ext.Fledge
 		anyBidsReturned bool
 		// List of bidders we have requests for.
-		liveAdapters []openrtb_ext.BidderName
+		liveAdapters      []openrtb_ext.BidderName
+		seatNonBidBuilder SeatNonBidBuilder = SeatNonBidBuilder{}
 	)
 
 	if len(r.StoredAuctionResponses) > 0 {
@@ -379,13 +403,15 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		fledge = extraRespInfo.fledge
 		anyBidsReturned = extraRespInfo.bidsFound
 		r.BidderResponseStartTime = extraRespInfo.bidderResponseStartTime
+		if extraRespInfo.seatNonBidBuilder != nil {
+			seatNonBidBuilder = extraRespInfo.seatNonBidBuilder
+		}
 	}
 
 	var (
 		auc            *auction
 		cacheErrs      []error
 		bidResponseExt *openrtb_ext.ExtBidResponse
-		seatNonBids    = nonBids{}
 	)
 
 	if anyBidsReturned {
@@ -399,6 +425,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 				errs = append(errs, &errortypes.Warning{
 					Message:     fmt.Sprintf("%s bid id %s rejected - bid price %.4f %s is less than bid floor %.4f %s for imp %s", rejectedBid.Seat, rejectedBid.Bids[0].Bid.ID, rejectedBid.Bids[0].Bid.Price, rejectedBid.Currency, rejectedBid.Bids[0].BidFloors.FloorValue, rejectedBid.Bids[0].BidFloors.FloorCurrency, rejectedBid.Bids[0].Bid.ImpID),
 					WarningCode: errortypes.FloorBidRejectionWarningCode})
+				rejectionReason := ResponseRejectedBelowFloor
+				if rejectedBid.Bids[0].Bid.DealID != "" {
+					rejectionReason = ResponseRejectedBelowDealFloor
+				}
+				seatNonBidBuilder.rejectBid(rejectedBid.Bids[0], int(rejectionReason), rejectedBid.Seat)
 			}
 		}
 
@@ -406,7 +437,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		//If includebrandcategory is present in ext then CE feature is on.
 		if requestExtPrebid.Targeting != nil && requestExtPrebid.Targeting.IncludeBrandCategory != nil {
 			var rejections []string
-			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBids)
+			bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, *requestExtPrebid.Targeting, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &seatNonBidBuilder)
 			if err != nil {
 				return nil, fmt.Errorf("Error in category mapping : %s", err.Error())
 			}
@@ -416,10 +447,11 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 		}
 
 		if e.bidIDGenerator.Enabled() {
-			for _, seatBid := range adapterBids {
-				for _, pbsBid := range seatBid.Bids {
-					pbsBid.GeneratedBidID, err = e.bidIDGenerator.New()
-					if err != nil {
+			for bidder, seatBid := range adapterBids {
+				for i := range seatBid.Bids {
+					if bidID, err := e.bidIDGenerator.New(bidder.String()); err == nil {
+						seatBid.Bids[i].GeneratedBidID = bidID
+					} else {
 						errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid"))
 					}
 				}
@@ -459,7 +491,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 				errs = append(errs, cacheErrs...)
 			}
 
-			targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap)
+			if targData.includeWinners || targData.includeBidderKeys || targData.includeFormat {
+				targData.setTargeting(auc, r.BidRequestWrapper.BidRequest.App != nil, bidCategory, r.Account.TruncateTargetAttribute, multiBidMap)
+			}
 		}
 		bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, *r, responseDebugAllow, requestExtPrebid.Passthrough, fledge, errs)
 	} else {
@@ -485,6 +519,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 	}
 
 	for _, warning := range r.Warnings {
+		if errortypes.ReadScope(warning) == errortypes.ScopeDebug && !responseDebugAllow {
+			continue
+		}
 		generalWarning := openrtb_ext.ExtBidderMessage{
 			Code:    errortypes.ReadCode(warning),
 			Message: warning.Error(),
@@ -495,14 +532,14 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
 	e.bidValidationEnforcement.SetBannerCreativeMaxSize(r.Account.Validations)
 
 	// Build the response
-	bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs, &seatNonBids)
+	bidResponse := e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequestWrapper, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, r.ImpExtInfoMap, r.PubID, errs, &seatNonBidBuilder)
 	bidResponse = adservertargeting.Apply(r.BidRequestWrapper, r.ResolvedBidRequest, bidResponse, r.QueryParams, bidResponseExt, r.Account.TruncateTargetAttribute)
 
 	bidResponse.Ext, err = encodeBidResponseExt(bidResponseExt)
 	if err != nil {
 		return nil, err
 	}
-	bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBids)
+	bidResponseExt = setSeatNonBid(bidResponseExt, seatNonBidBuilder)
 
 	return &AuctionResponse{
 		BidResponse:    bidResponse,
@@ -682,7 +719,7 @@ func (e *exchange) getAllBids(
 	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, len(bidderRequests))
 	adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests))
 	chBids := make(chan *bidResponseWrapper, len(bidderRequests))
-	extraRespInfo := extraAuctionResponseInfo{}
+	extraRespInfo := extraAuctionResponseInfo{seatNonBidBuilder: SeatNonBidBuilder{}}
 
 	e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime))
 
@@ -722,6 +759,7 @@ func (e *exchange) getAllBids(
 			// Add in time reporting
 			elapsed := time.Since(start)
 			brw.adapterSeatBids = seatBids
+			brw.seatNonBidBuilder = extraBidderRespInfo.seatNonBidBuilder
 			// Structure to record extra tracking data generated during bidding
 			ae := new(seatResponseExtra)
 			ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
@@ -774,6 +812,10 @@ func (e *exchange) getAllBids(
 		}
 		//but we need to add all bidders data to adapterExtra to have metrics and other metadata
 		adapterExtra[brw.bidder] = brw.adapterExtra
+
+		// collect adapter non bids
+		extraRespInfo.seatNonBidBuilder.append(brw.seatNonBidBuilder)
+
 	}
 
 	return adapterBids, adapterExtra, extraRespInfo
@@ -891,7 +933,7 @@ func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage {
 }
 
 // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester
-func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb_ext.RequestWrapper, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error, seatNonBids *nonBids) *openrtb2.BidResponse {
+func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterSeatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, bidRequest *openrtb_ext.RequestWrapper, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, pubID string, errList []error, seatNonBidBuilder *SeatNonBidBuilder) *openrtb2.BidResponse {
 	bidResponse := new(openrtb2.BidResponse)
 
 	bidResponse.ID = bidRequest.ID
@@ -906,7 +948,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 	for a, adapterSeatBids := range adapterSeatBids {
 		//while processing every single bib, do we need to handle categories here?
 		if adapterSeatBids != nil && len(adapterSeatBids.Bids) > 0 {
-			sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, pubID, seatNonBids)
+			sb := e.makeSeatBid(adapterSeatBids, a, adapterExtra, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, pubID, seatNonBidBuilder)
 			seatBids = append(seatBids, *sb)
 			bidResponse.Cur = adapterSeatBids.Currency
 		}
@@ -926,7 +968,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e
 	return buffer.Bytes(), err
 }
 
-func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBids *nonBids) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) {
+func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestTargeting, seatBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator, seatNonBidBuilder *SeatNonBidBuilder) (map[string]string, map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, []string, error) {
 	res := make(map[string]string)
 
 	type bidDedupe struct {
@@ -988,7 +1030,7 @@ func applyCategoryMapping(ctx context.Context, targeting openrtb_ext.ExtRequestT
 					//on receiving bids from adapters if no unique IAB category is returned  or if no ad server category is returned discard the bid
 					bidsToRemove = append(bidsToRemove, bidInd)
 					rejections = updateRejections(rejections, bidID, "Bid did not contain a category")
-					seatNonBids.addBid(bid, int(ResponseRejectedCategoryMappingInvalid), string(bidderName))
+					seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCategoryMappingInvalid), string(bidderName))
 					continue
 				}
 				if translateCategories {
@@ -1225,14 +1267,14 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*en
 
 // Return an openrtb seatBid for a bidder
 // buildBidResponse is responsible for ensuring nil bid seatbids are not included
-func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string, seatNonBids *nonBids) *openrtb2.SeatBid {
+func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, pubID string, seatNonBidBuilder *SeatNonBidBuilder) *openrtb2.SeatBid {
 	seatBid := &openrtb2.SeatBid{
 		Seat:  adapter.String(),
 		Group: 0, // Prebid cannot support roadblocking
 	}
 
 	var errList []error
-	seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, adapter, pubID, seatNonBids)
+	seatBid.Bid, errList = e.makeBid(adapterBid.Bids, auc, returnCreative, impExtInfoMap, bidRequest, bidResponseExt, adapter, pubID, seatNonBidBuilder)
 	if len(errList) > 0 {
 		adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, errsToBidderErrors(errList)...)
 	}
@@ -1240,24 +1282,24 @@ func (e *exchange) makeSeatBid(adapterBid *entities.PbsOrtbSeatBid, adapter open
 	return seatBid
 }
 
-func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, seatNonBids *nonBids) ([]openrtb2.Bid, []error) {
+func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCreative bool, impExtInfoMap map[string]ImpExtInfo, bidRequest *openrtb_ext.RequestWrapper, bidResponseExt *openrtb_ext.ExtBidResponse, adapter openrtb_ext.BidderName, pubID string, seatNonBidBuilder *SeatNonBidBuilder) ([]openrtb2.Bid, []error) {
 	result := make([]openrtb2.Bid, 0, len(bids))
 	errs := make([]error, 0, 1)
 
 	for _, bid := range bids {
-		if !dsa.Validate(bidRequest, bid) {
-			RequiredError := openrtb_ext.ExtBidderMessage{
+		if err := dsa.Validate(bidRequest, bid); err != nil {
+			dsaMessage := openrtb_ext.ExtBidderMessage{
 				Code:    errortypes.InvalidBidResponseDSAWarningCode,
-				Message: "bid response rejected: DSA object missing when required",
+				Message: fmt.Sprintf("bid rejected: %s", err.Error()),
 			}
-			bidResponseExt.Warnings[adapter] = append(bidResponseExt.Warnings[adapter], RequiredError)
+			bidResponseExt.Warnings[adapter] = append(bidResponseExt.Warnings[adapter], dsaMessage)
 
-			seatNonBids.addBid(bid, int(ResponseRejectedGeneral), adapter.String())
+			seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedGeneral), adapter.String())
 			continue // Don't add bid to result
 		}
 		if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationEnforce && bid.BidType == openrtb_ext.BidTypeBanner {
 			if !e.validateBannerCreativeSize(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.BannerCreativeMaxSize) {
-				seatNonBids.addBid(bid, int(ResponseRejectedCreativeSizeNotAllowed), adapter.String())
+				seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCreativeSizeNotAllowed), adapter.String())
 				continue // Don't add bid to result
 			}
 		} else if e.bidValidationEnforcement.BannerCreativeMaxSize == config.ValidationWarn && bid.BidType == openrtb_ext.BidTypeBanner {
@@ -1266,7 +1308,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea
 		if _, ok := impExtInfoMap[bid.Bid.ImpID]; ok {
 			if e.bidValidationEnforcement.SecureMarkup == config.ValidationEnforce && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) {
 				if !e.validateBidAdM(bid, bidResponseExt, adapter, pubID, e.bidValidationEnforcement.SecureMarkup) {
-					seatNonBids.addBid(bid, int(ResponseRejectedCreativeNotSecure), adapter.String())
+					seatNonBidBuilder.rejectBid(bid, int(ResponseRejectedCreativeNotSecure), adapter.String())
 					continue // Don't add bid to result
 				}
 			} else if e.bidValidationEnforcement.SecureMarkup == config.ValidationWarn && (bid.BidType == openrtb_ext.BidTypeBanner || bid.BidType == openrtb_ext.BidTypeVideo) {
@@ -1293,7 +1335,7 @@ func (e *exchange) makeBid(bids []*entities.PbsOrtbBid, auc *auction, returnCrea
 			}
 		}
 
-		if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, adapter); err != nil {
+		if bidExtJSON, err := makeBidExtJSON(bid.Bid.Ext, bidExtPrebid, impExtInfoMap, bid.Bid.ImpID, bid.OriginalBidCPM, bid.OriginalBidCur, bid.AdapterCode); err != nil {
 			errs = append(errs, err)
 		} else {
 			result = append(result, *bid.Bid)
@@ -1566,8 +1608,8 @@ func setErrorMessageSecureMarkup(validationType string) string {
 }
 
 // setSeatNonBid adds SeatNonBids within bidResponse.Ext.Prebid.SeatNonBid
-func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBids) *openrtb_ext.ExtBidResponse {
-	if len(seatNonBids.seatNonBidsMap) == 0 {
+func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBidBuilder SeatNonBidBuilder) *openrtb_ext.ExtBidResponse {
+	if len(seatNonBidBuilder) == 0 {
 		return bidResponseExt
 	}
 	if bidResponseExt == nil {
@@ -1577,6 +1619,6 @@ func setSeatNonBid(bidResponseExt *openrtb_ext.ExtBidResponse, seatNonBids nonBi
 		bidResponseExt.Prebid = &openrtb_ext.ExtResponsePrebid{}
 	}
 
-	bidResponseExt.Prebid.SeatNonBid = seatNonBids.get()
+	bidResponseExt.Prebid.SeatNonBid = seatNonBidBuilder.Slice()
 	return bidResponseExt
 }
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index e484e21a42e..7014448f14d 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -35,10 +35,12 @@ import (
 	metricsConf "github.com/prebid/prebid-server/v2/metrics/config"
 	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/ortb"
 	pbc "github.com/prebid/prebid-server/v2/prebid_cache_client"
 	"github.com/prebid/prebid-server/v2/privacy"
 	"github.com/prebid/prebid-server/v2/stored_requests"
 	"github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher"
+	"github.com/prebid/prebid-server/v2/stored_responses"
 	"github.com/prebid/prebid-server/v2/usersync"
 	"github.com/prebid/prebid-server/v2/util/jsonutil"
 	"github.com/prebid/prebid-server/v2/util/ptrutil"
@@ -82,7 +84,7 @@ func TestNewExchange(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
 			if biddersInfo[string(bidderName)].IsEnabled() {
@@ -132,7 +134,7 @@ func TestCharacterEscape(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	//liveAdapters []openrtb_ext.BidderName,
@@ -170,7 +172,7 @@ func TestCharacterEscape(t *testing.T) {
 	var errList []error
 
 	// 	4) Build bid response
-	bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList, &nonBids{})
+	bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList, &SeatNonBidBuilder{})
 
 	// 	5) Assert we have no errors and one '&' character as we are supposed to
 	if len(errList) > 0 {
@@ -365,7 +367,7 @@ func TestDebugBehaviour(t *testing.T) {
 		if test.generateWarnings {
 			var errL []error
 			errL = append(errL, &errortypes.Warning{
-				Message:     fmt.Sprintf("CCPA consent test warning."),
+				Message:     "CCPA consent test warning.",
 				WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
 			auctionRequest.Warnings = errL
 		}
@@ -663,7 +665,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 	}.Builder
 	e.currencyConverter = mockCurrencyConverter
 	e.categoriesFetcher = categoriesFetcher
-	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+	e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}
 	e.requestSplitter = requestSplitter{
 		me:               e.me,
 		gdprPermsBuilder: e.gdprPermsBuilder,
@@ -768,7 +770,7 @@ func TestAdapterCurrency(t *testing.T) {
 		}.Builder,
 		currencyConverter: currencyConverter,
 		categoriesFetcher: nilCategoryFetcher{},
-		bidIDGenerator:    &mockBidIDGenerator{false, false},
+		bidIDGenerator:    &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false},
 		adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{
 			openrtb_ext.BidderName("appnexus"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("appnexus"), nil, ""),
 		},
@@ -846,7 +848,7 @@ func TestFloorsSignalling(t *testing.T) {
 		}.Builder,
 		currencyConverter: currencyConverter,
 		categoriesFetcher: nilCategoryFetcher{},
-		bidIDGenerator:    &mockBidIDGenerator{false, false},
+		bidIDGenerator:    &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false},
 		priceFloorEnabled: true,
 		priceFloorFetcher: &mockPriceFloorFetcher{},
 	}
@@ -978,6 +980,7 @@ func TestFloorsSignalling(t *testing.T) {
 			Account:           config.Account{DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: test.floorsEnable, MaxRule: 100, MaxSchemaDims: 5}},
 			UserSyncs:         &emptyUsersync{},
 			HookExecutor:      &hookexecution.EmptyHookExecutor{},
+			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
 		}
 		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
 
@@ -1129,7 +1132,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 	}.Builder
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
-	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+	e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}
 	e.requestSplitter = requestSplitter{
 		me:               e.me,
 		gdprPermsBuilder: e.gdprPermsBuilder,
@@ -1234,7 +1237,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, pbc, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
 
@@ -1340,7 +1343,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	var errList []error
 
 	// 	4) Build bid response
-	bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList, &nonBids{})
+	bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList, &SeatNonBidBuilder{})
 
 	expectedBidResponse := &openrtb2.BidResponse{
 		SeatBid: []openrtb2.SeatBid{
@@ -1430,7 +1433,7 @@ func TestBidReturnsCreative(t *testing.T) {
 
 	//Run tests
 	for _, test := range testCases {
-		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, &openrtb_ext.RequestWrapper{}, nil, "", "", &nonBids{})
+		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, &openrtb_ext.RequestWrapper{}, nil, "", "", &SeatNonBidBuilder{})
 
 		assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description)
 		assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description)
@@ -1593,7 +1596,7 @@ func TestBidResponseCurrency(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1637,7 +1640,7 @@ func TestBidResponseCurrency(t *testing.T) {
 					Price: 9.517803,
 					W:     300,
 					H:     250,
-					Ext:   json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"banner"}}`),
+					Ext:   json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"meta":{},"type":"banner"}}`),
 				},
 			},
 		},
@@ -1715,7 +1718,7 @@ func TestBidResponseCurrency(t *testing.T) {
 	}
 	// Run tests
 	for i := range testCases {
-		actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList, &nonBids{})
+		actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList, &SeatNonBidBuilder{})
 		assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext))
 	}
 }
@@ -1741,7 +1744,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{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1767,7 +1770,7 @@ func TestBidResponseImpExtInfo(t *testing.T) {
 		H:     250,
 		Ext:   nil,
 	}
-	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo}}
+	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo, AdapterCode: openrtb_ext.BidderAppnexus}}
 
 	adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
 		openrtb_ext.BidderName("appnexus"): {
@@ -1783,7 +1786,7 @@ func TestBidResponseImpExtInfo(t *testing.T) {
 
 	expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`
 
-	actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList, &nonBids{})
+	actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList, &SeatNonBidBuilder{})
 
 	resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext)
 	assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect")
@@ -1835,7 +1838,7 @@ func TestRaceIntegration(t *testing.T) {
 		},
 	}.Builder
 
-	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 	_, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
@@ -1933,7 +1936,7 @@ func TestPanicRecovery(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	chBids := make(chan *bidResponseWrapper, 1)
 	panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) {
@@ -2003,7 +2006,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{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, &mockCache{}, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
 	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
@@ -2105,8 +2108,8 @@ func loadFile(filename string) (*exchangeSpec, error) {
 }
 
 func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
-	aliases, errs := parseAliases(&spec.IncomingRequest.OrtbRequest)
-	if len(errs) != 0 {
+	aliases, err := parseRequestAliases(spec.IncomingRequest.OrtbRequest)
+	if err != nil {
 		t.Fatalf("%s: Failed to parse aliases", filename)
 	}
 
@@ -2139,11 +2142,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 			},
 		},
 	}
-	bidIdGenerator := &mockBidIDGenerator{}
+	bidIdGenerator := &fakeBidIDGenerator{}
 	if spec.BidIDGenerator != nil {
-		*bidIdGenerator = *spec.BidIDGenerator
+		bidIdGenerator = spec.BidIDGenerator
 	}
-	ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator, spec.HostSChainFlag, spec.FloorsEnabled, spec.HostConfigBidValidation, spec.Server)
+	ex := newExchangeForTests(t, filename, aliases, privacyConfig, bidIdGenerator, spec)
 	biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest)
 	debugLog := &DebugLog{}
 	if spec.DebugLog != nil {
@@ -2170,7 +2173,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		impExtInfoMap[impID] = ImpExtInfo{}
 	}
 
-	activityControl := privacy.NewActivityControl(spec.AccountPrivacy)
+	if spec.AccountPrivacy.DSA != nil && len(spec.AccountPrivacy.DSA.Default) > 0 {
+		if err := jsonutil.Unmarshal([]byte(spec.AccountPrivacy.DSA.Default), &spec.AccountPrivacy.DSA.DefaultUnpacked); err != nil {
+			t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error())
+		}
+	}
+
+	activityControl := privacy.NewActivityControl(&spec.AccountPrivacy)
 
 	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest},
@@ -2180,7 +2189,8 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 				Enabled: spec.EventsEnabled,
 			},
 			DebugAllow:  true,
-			PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled},
+			PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled, EnforceDealFloors: spec.AccountEnforceDealFloors},
+			Privacy:     spec.AccountPrivacy,
 			Validations: spec.AccountConfigBidValidation,
 		},
 		UserSyncs:     mockIdFetcher(spec.IncomingRequest.Usersyncs),
@@ -2327,7 +2337,8 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 }
 
 func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string {
-	if splitImps, err := splitImps(req.Imp); err != nil {
+
+	if splitImps, err := splitImps(req.Imp, &mockRequestValidator{}, nil, false, nil); err != nil {
 		t.Errorf("%s: Failed to parse Bidders from request: %v", context, err)
 		return nil
 	} else {
@@ -2363,11 +2374,11 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidRespons
 	}
 }
 
-func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, hostSChainFlag, floorsFlag bool, hostBidValidation config.Validations, server exchangeServer) Exchange {
-	bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations))
-	bidderInfos := make(config.BidderInfos, len(expectations))
+func newExchangeForTests(t *testing.T, filename string, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, exSpec *exchangeSpec) Exchange {
+	bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(exSpec.OutgoingRequests))
+	bidderInfos := make(config.BidderInfos, len(exSpec.OutgoingRequests))
 	for _, bidderName := range openrtb_ext.CoreBidderNames() {
-		if spec, ok := expectations[string(bidderName)]; ok {
+		if spec, ok := exSpec.OutgoingRequests[string(bidderName)]; ok {
 			bidderAdapters[bidderName] = &validatingBidder{
 				t:             t,
 				fileName:      filename,
@@ -2375,12 +2386,18 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 				expectations:  map[string]*bidderRequest{string(bidderName): spec.ExpectedRequest},
 				mockResponses: map[string]bidderResponse{string(bidderName): spec.MockResponse},
 			}
-			bidderInfos[string(bidderName)] = config.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed}
+			ortbVersion, _ := exSpec.ORTBVersion[string(bidderName)]
+			bidderInfos[string(bidderName)] = config.BidderInfo{
+				ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed,
+				OpenRTB:                 &config.OpenRTBInfo{Version: ortbVersion},
+			}
+		} else {
+			bidderInfos[string(bidderName)] = config.BidderInfo{}
 		}
 	}
 
 	for alias, coreBidder := range aliases {
-		if spec, ok := expectations[alias]; ok {
+		if spec, ok := exSpec.OutgoingRequests[alias]; ok {
 			if bidder, ok := bidderAdapters[openrtb_ext.BidderName(coreBidder)]; ok {
 				bidder.(*validatingBidder).expectations[alias] = spec.ExpectedRequest
 				bidder.(*validatingBidder).mockResponses[alias] = spec.MockResponse
@@ -2418,13 +2435,19 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 	}
 
 	var hostSChainNode *openrtb2.SupplyChainNode
-	if hostSChainFlag {
+	if exSpec.HostSChainFlag {
 		hostSChainNode = &openrtb2.SupplyChainNode{
 			ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1),
 		}
 	}
 
 	metricsEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil)
+	paramValidator, err := openrtb_ext.NewBidderParamsValidator("../static/bidder-params")
+	if err != nil {
+		t.Fatalf("Failed to create params validator: %v", error)
+	}
+	bidderMap := GetActiveBidders(bidderInfos)
+	requestValidator := ortb.NewRequestValidator(bidderMap, map[string]string{}, paramValidator)
 	requestSplitter := requestSplitter{
 		bidderToSyncerKey: bidderToSyncerKey,
 		me:                metricsEngine,
@@ -2432,6 +2455,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		gdprPermsBuilder:  gdprPermsBuilder,
 		hostSChainNode:    hostSChainNode,
 		bidderInfo:        bidderInfos,
+		requestValidator:  requestValidator,
 	}
 
 	return &exchange{
@@ -2449,39 +2473,43 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		externalURL:              "http://localhost",
 		bidIDGenerator:           bidIDGenerator,
 		hostSChainNode:           hostSChainNode,
-		server:                   config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter},
-		bidValidationEnforcement: hostBidValidation,
+		server:                   config.Server{ExternalUrl: exSpec.Server.ExternalUrl, GvlID: exSpec.Server.GvlID, DataCenter: exSpec.Server.DataCenter},
+		bidValidationEnforcement: exSpec.HostConfigBidValidation,
 		requestSplitter:          requestSplitter,
-		priceFloorEnabled:        floorsFlag,
+		priceFloorEnabled:        exSpec.FloorsEnabled,
 		priceFloorFetcher:        &mockPriceFloorFetcher{},
 	}
 }
 
-type mockBidIDGenerator struct {
+type fakeBidIDGenerator struct {
 	GenerateBidID bool `json:"generateBidID"`
 	ReturnError   bool `json:"returnError"`
+	bidCount      map[string]int
 }
 
-func (big *mockBidIDGenerator) Enabled() bool {
-	return big.GenerateBidID
+func (f *fakeBidIDGenerator) Enabled() bool {
+	return f.GenerateBidID
 }
 
-func (big *mockBidIDGenerator) New() (string, error) {
+func (f *fakeBidIDGenerator) New(bidder string) (string, error) {
+	if f.ReturnError {
+		return "", errors.New("Test error generating bid.ext.prebid.bidid")
+	}
 
-	if big.ReturnError {
-		err := errors.New("Test error generating bid.ext.prebid.bidid")
-		return "", err
+	if f.bidCount == nil {
+		f.bidCount = make(map[string]int)
 	}
-	return "mock_uuid", nil
 
+	f.bidCount[bidder] += 1
+	return fmt.Sprintf("bid-%v-%v", bidder, f.bidCount[bidder]), nil
 }
 
-type fakeRandomDeduplicateBidBooleanGenerator struct {
-	returnValue bool
+type fakeBooleanGenerator struct {
+	value bool
 }
 
-func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool {
-	return m.returnValue
+func (f *fakeBooleanGenerator) Generate() bool {
+	return f.value
 }
 
 func newExtRequest() openrtb_ext.ExtRequest {
@@ -2566,10 +2594,10 @@ func TestCategoryMapping(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""}
-	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 40.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 40.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -2583,7 +2611,7 @@ func TestCategoryMapping(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
 	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
@@ -2621,10 +2649,10 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""}
-	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 40.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 40.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -2638,7 +2666,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
 	assert.Empty(t, rejections, "There should be no bid rejection messages")
@@ -2675,9 +2703,9 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -2690,7 +2718,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
 	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
@@ -2757,9 +2785,9 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 30.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -2772,7 +2800,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
 
 	adapterBids[bidderName1] = &seatBid
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.Equal(t, nil, err, "Category mapping error should be empty")
 	assert.Empty(t, rejections, "There should be no bid rejection messages")
@@ -2849,8 +2877,8 @@ func TestCategoryDedupe(t *testing.T) {
 					Currency: "USD",
 				},
 			}
-			deduplicateGenerator := fakeRandomDeduplicateBidBooleanGenerator{returnValue: tt.dedupeGeneratorValue}
-			bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &nonBids{})
+			deduplicateGenerator := fakeBooleanGenerator{value: tt.dedupeGeneratorValue}
+			bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &deduplicateGenerator, &SeatNonBidBuilder{})
 
 			assert.Nil(t, err)
 			assert.Equal(t, 3, len(rejections))
@@ -2885,11 +2913,11 @@ func TestNoCategoryDedupe(t *testing.T) {
 	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
 	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""}
-	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 14.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 14.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_3 := entities.PbsOrtbBid{Bid: &bid3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_4 := entities.PbsOrtbBid{Bid: &bid4, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_5 := entities.PbsOrtbBid{Bid: &bid5, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	selectedBids := make(map[string]int)
 	expectedCategories := map[string]string{
@@ -2919,7 +2947,7 @@ func TestNoCategoryDedupe(t *testing.T) {
 
 		adapterBids[bidderName1] = &seatBid
 
-		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+		bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 		assert.Equal(t, nil, err, "Category mapping error should be empty")
 		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
@@ -2965,8 +2993,8 @@ func TestCategoryMappingBidderName(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids1 := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -2984,7 +3012,7 @@ func TestCategoryMappingBidderName(t *testing.T) {
 	adapterBids[bidderName1] = &seatBid1
 	adapterBids[bidderName2] = &seatBid2
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.NoError(t, err, "Category mapping error should be empty")
 	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
@@ -3019,8 +3047,8 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, nil, nil, 0, false, "", 12.0000, "USD", ""}
+	bid1_1 := entities.PbsOrtbBid{Bid: &bid1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_2 := entities.PbsOrtbBid{Bid: &bid2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 12.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBids1 := []*entities.PbsOrtbBid{
 		&bid1_1,
@@ -3038,7 +3066,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
 	adapterBids[bidderName1] = &seatBid1
 	adapterBids[bidderName2] = &seatBid2
 
-	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+	bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 	assert.NoError(t, err, "Category mapping error should be empty")
 	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
@@ -3131,7 +3159,7 @@ func TestBidRejectionErrors(t *testing.T) {
 		innerBids := []*entities.PbsOrtbBid{}
 		for _, bid := range test.bids {
 			currentBid := entities.PbsOrtbBid{
-				bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, nil, 0, false, "", 10.0000, "USD", ""}
+				Bid: bid, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 			innerBids = append(innerBids, &currentBid)
 		}
 
@@ -3139,7 +3167,7 @@ func TestBidRejectionErrors(t *testing.T) {
 
 		adapterBids[bidderName] = &seatBid
 
-		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+		bidCategory, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 		if len(test.expectedCatDur) > 0 {
 			// Bid deduplication case
@@ -3179,8 +3207,8 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 	bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 	bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
+	bid1_Apn1 := entities.PbsOrtbBid{Bid: &bidApn1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_Apn2 := entities.PbsOrtbBid{Bid: &bidApn2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBidsApn1 := []*entities.PbsOrtbBid{
 		&bid1_Apn1,
@@ -3202,7 +3230,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
 		adapterBids[bidderNameApn1] = &seatBidApn1
 		adapterBids[bidderNameApn2] = &seatBidApn2
 
-		bidCategory, _, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
+		bidCategory, _, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &SeatNonBidBuilder{})
 
 		assert.NoError(t, err, "Category mapping error should be empty")
 		assert.Len(t, rejections, 1, "There should be 1 bid rejection message")
@@ -3259,11 +3287,11 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T)
 	bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1}
 	bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
 
-	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
+	bid1_Apn1_1 := entities.PbsOrtbBid{Bid: &bidApn1_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_Apn1_2 := entities.PbsOrtbBid{Bid: &bidApn1_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
-	bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
+	bid1_Apn2_1 := entities.PbsOrtbBid{Bid: &bidApn2_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_Apn2_2 := entities.PbsOrtbBid{Bid: &bidApn2_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	innerBidsApn1 := []*entities.PbsOrtbBid{
 		&bid1_Apn1_1,
@@ -3286,7 +3314,7 @@ func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T)
 	adapterBids[bidderNameApn1] = &seatBidApn1
 	adapterBids[bidderNameApn2] = &seatBidApn2
 
-	_, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}, &nonBids{})
+	_, adapterBids, rejections, err := applyCategoryMapping(context.TODO(), *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeBooleanGenerator{value: true}, &SeatNonBidBuilder{})
 
 	assert.NoError(t, err, "Category mapping error should be empty")
 
@@ -3341,9 +3369,9 @@ func TestRemoveBidById(t *testing.T) {
 	bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
 	bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
 
-	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
-	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
-	bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
+	bid1_Apn1_1 := entities.PbsOrtbBid{Bid: &bidApn1_1, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_Apn1_2 := entities.PbsOrtbBid{Bid: &bidApn1_2, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 20.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
+	bid1_Apn1_3 := entities.PbsOrtbBid{Bid: &bidApn1_3, BidMeta: nil, BidType: "video", BidTargets: nil, BidVideo: &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, BidEvents: nil, BidFloors: nil, DealPriority: 0, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 10.0000, OriginalBidCur: "USD", TargetBidderCode: ""}
 
 	type aTest struct {
 		desc      string
@@ -3544,7 +3572,7 @@ func TestApplyDealSupport(t *testing.T) {
 			},
 		}
 
-		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.in.dealPriority, false, "", 0, "USD", ""}
+		bid := entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: test.in.dealPriority, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}
 		bidCategory := map[string]string{
 			bid.Bid.ID: test.in.targ["hb_pb_cat_dur"],
 		}
@@ -3604,8 +3632,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
 					allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
 						"imp_id1": {
 							openrtb_ext.BidderName("appnexus"): {
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
 							},
 						},
 					},
@@ -3650,8 +3678,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
 					allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
 						"imp_id1": {
 							openrtb_ext.BidderName("appnexus"): {
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
 							},
 						},
 					},
@@ -3701,8 +3729,8 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
 					allBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
 						"imp_id1": {
 							openrtb_ext.BidderName("appnexus"): {
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
-								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
+								&entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "789101"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: 5, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""},
 							},
 						},
 					},
@@ -3893,7 +3921,7 @@ func TestUpdateHbPbCatDur(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""}
+		bid := entities.PbsOrtbBid{Bid: &openrtb2.Bid{ID: "123456"}, BidMeta: nil, BidType: "video", BidTargets: map[string]string{}, BidVideo: &openrtb_ext.ExtBidPrebidVideo{}, BidEvents: nil, BidFloors: nil, DealPriority: test.dealPriority, DealTierSatisfied: false, GeneratedBidID: "", OriginalBidCPM: 0, OriginalBidCur: "USD", TargetBidderCode: ""}
 		bidCategory := map[string]string{
 			bid.Bid.ID: test.targ["hb_pb_cat_dur"],
 		}
@@ -4110,7 +4138,7 @@ func TestStoredAuctionResponses(t *testing.T) {
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
 	e.categoriesFetcher = categoriesFetcher
-	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+	e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.gdprPermsBuilder = fakePermissionsBuilder{
 		permissions: &permissionsMock{
@@ -4132,7 +4160,7 @@ func TestStoredAuctionResponses(t *testing.T) {
 		SeatBid: []openrtb2.SeatBid{
 			{
 				Bid: []openrtb2.Bid{
-					{ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"meta":{"adaptercode":"appnexus"},"type":"video"}}`)},
+					{ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"meta":{},"type":"video"}}`)},
 				},
 				Seat: "appnexus",
 			},
@@ -4476,7 +4504,7 @@ func TestAuctionDebugEnabled(t *testing.T) {
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
 	e.categoriesFetcher = categoriesFetcher
-	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+	e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.gdprPermsBuilder = fakePermissionsBuilder{
 		permissions: &permissionsMock{
@@ -4553,7 +4581,7 @@ func TestPassExperimentConfigsToHoldAuction(t *testing.T) {
 		},
 	}.Builder
 
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
+	e := NewExchange(adapters, nil, cfg, &mockRequestValidator{}, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer(), nil).(*exchange)
 
 	// Define mock incoming bid requeset
 	mockBidRequest := &openrtb2.BidRequest{
@@ -4750,7 +4778,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 		givenBids                []*entities.PbsOrtbBid
 		givenSeat                openrtb_ext.BidderName
 		expectedNumOfBids        int
-		expectedNonBids          *nonBids
+		expectedNonBids          *SeatNonBidBuilder
 		expectedNumDebugErrors   int
 		expectedNumDebugWarnings int
 	}{
@@ -4758,18 +4786,16 @@ func TestMakeBidWithValidation(t *testing.T) {
 			name:               "One_of_two_bids_is_invalid_based_on_DSA_object_presence",
 			givenBidRequestExt: json.RawMessage(`{"dsa": {"dsarequired": 2}}`),
 			givenValidations:   config.Validations{},
-			givenBids:          []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{Ext: json.RawMessage(`{"dsa": {}}`)}}, {Bid: &openrtb2.Bid{}}},
+			givenBids:          []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{Ext: json.RawMessage(`{"dsa": {"adrender":1}}`)}}, {Bid: &openrtb2.Bid{}}},
 			givenSeat:          "pubmatic",
 			expectedNumOfBids:  1,
-			expectedNonBids: &nonBids{
-				seatNonBidsMap: map[string][]openrtb_ext.NonBid{
-					"pubmatic": {
-						{
-							StatusCode: 300,
-							Ext: openrtb_ext.NonBidExt{
-								Prebid: openrtb_ext.ExtResponseNonBidPrebid{
-									Bid: openrtb_ext.NonBidObject{},
-								},
+			expectedNonBids: &SeatNonBidBuilder{
+				"pubmatic": {
+					{
+						StatusCode: 300,
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{},
 							},
 						},
 					},
@@ -4783,17 +4809,15 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:         "pubmatic",
 			expectedNumOfBids: 1,
-			expectedNonBids: &nonBids{
-				seatNonBidsMap: map[string][]openrtb_ext.NonBid{
-					"pubmatic": {
-						{
-							StatusCode: 351,
-							Ext: openrtb_ext.NonBidExt{
-								Prebid: openrtb_ext.ExtResponseNonBidPrebid{
-									Bid: openrtb_ext.NonBidObject{
-										W: 200,
-										H: 200,
-									},
+			expectedNonBids: &SeatNonBidBuilder{
+				"pubmatic": {
+					{
+						StatusCode: 351,
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{
+									W: 200,
+									H: 200,
 								},
 							},
 						},
@@ -4808,7 +4832,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:              []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:              "pubmatic",
 			expectedNumOfBids:      2,
-			expectedNonBids:        &nonBids{},
+			expectedNonBids:        &SeatNonBidBuilder{},
 			expectedNumDebugErrors: 1,
 		},
 		{
@@ -4817,12 +4841,15 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:         "pubmatic",
 			expectedNumOfBids: 1,
-			expectedNonBids: &nonBids{
-				seatNonBidsMap: map[string][]openrtb_ext.NonBid{
-					"pubmatic": {
-						{
-							ImpId:      "1",
-							StatusCode: 352,
+			expectedNonBids: &SeatNonBidBuilder{
+				"pubmatic": {
+					{
+						ImpId:      "1",
+						StatusCode: 352,
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{},
+							},
 						},
 					},
 				},
@@ -4835,7 +4862,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:              []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:              "pubmatic",
 			expectedNumOfBids:      2,
-			expectedNonBids:        &nonBids{},
+			expectedNonBids:        &SeatNonBidBuilder{},
 			expectedNumDebugErrors: 1,
 		},
 		{
@@ -4844,7 +4871,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid"}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:         "pubmatic",
 			expectedNumOfBids: 2,
-			expectedNonBids:   &nonBids{},
+			expectedNonBids:   &SeatNonBidBuilder{},
 		},
 		{
 			name:              "Creative_size_validation_skipped,_Adm_Validation_enforced,_one_of_two_bids_has_invalid_dimensions",
@@ -4852,7 +4879,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
 			givenSeat:         "pubmatic",
 			expectedNumOfBids: 2,
-			expectedNonBids:   &nonBids{},
+			expectedNonBids:   &SeatNonBidBuilder{},
 		},
 	}
 
@@ -4901,7 +4928,7 @@ func TestMakeBidWithValidation(t *testing.T) {
 			}
 			e.bidValidationEnforcement = test.givenValidations
 			sampleBids := test.givenBids
-			nonBids := &nonBids{}
+			nonBids := &SeatNonBidBuilder{}
 			resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, true, ImpExtInfoMap, bidRequest, bidExtResponse, test.givenSeat, "", nonBids)
 
 			assert.Equal(t, 0, len(resultingErrs))
@@ -5077,7 +5104,7 @@ func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
 	}.Builder
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
-	e.bidIDGenerator = &mockBidIDGenerator{false, false}
+	e.bidIDGenerator = &fakeBidIDGenerator{GenerateBidID: false, ReturnError: false}
 	e.requestSplitter = requestSplitter{
 		me:               e.me,
 		gdprPermsBuilder: e.gdprPermsBuilder,
@@ -5219,6 +5246,7 @@ func TestGetAllBids(t *testing.T) {
 									ID: "1",
 								},
 								OriginalBidCur: "USD",
+								AdapterCode:    openrtb_ext.BidderPubmatic,
 							},
 						},
 						Currency:  "USD",
@@ -5287,6 +5315,7 @@ func TestGetAllBids(t *testing.T) {
 									ID: "1",
 								},
 								OriginalBidCur: "USD",
+								AdapterCode:    openrtb_ext.BidderPubmatic,
 							},
 						},
 						Currency:  "USD",
@@ -5300,6 +5329,7 @@ func TestGetAllBids(t *testing.T) {
 									ID: "2",
 								},
 								OriginalBidCur: "USD",
+								AdapterCode:    openrtb_ext.BidderPubmatic,
 							},
 						},
 						Currency:  "USD",
@@ -5367,6 +5397,7 @@ func TestGetAllBids(t *testing.T) {
 									ID: "2",
 								},
 								OriginalBidCur: "USD",
+								AdapterCode:    openrtb_ext.BidderPubmatic,
 							},
 						},
 						Currency:  "USD",
@@ -5474,17 +5505,19 @@ type exchangeSpec struct {
 	DebugLog                   *DebugLog              `json:"debuglog,omitempty"`
 	EventsEnabled              bool                   `json:"events_enabled,omitempty"`
 	StartTime                  int64                  `json:"start_time_ms,omitempty"`
-	BidIDGenerator             *mockBidIDGenerator    `json:"bidIDGenerator,omitempty"`
+	BidIDGenerator             *fakeBidIDGenerator    `json:"bidIDGenerator,omitempty"`
 	RequestType                *metrics.RequestType   `json:"requestType,omitempty"`
 	PassthroughFlag            bool                   `json:"passthrough_flag,omitempty"`
 	HostSChainFlag             bool                   `json:"host_schain_flag,omitempty"`
 	HostConfigBidValidation    config.Validations     `json:"host_bid_validations"`
 	AccountConfigBidValidation config.Validations     `json:"account_bid_validations"`
 	AccountFloorsEnabled       bool                   `json:"account_floors_enabled"`
+	AccountEnforceDealFloors   bool                   `json:"account_enforce_deal_floors"`
 	FledgeEnabled              bool                   `json:"fledge_enabled,omitempty"`
 	MultiBid                   *multiBidSpec          `json:"multiBid,omitempty"`
 	Server                     exchangeServer         `json:"server,omitempty"`
-	AccountPrivacy             *config.AccountPrivacy `json:"accountPrivacy,omitempty"`
+	AccountPrivacy             config.AccountPrivacy  `json:"accountPrivacy,omitempty"`
+	ORTBVersion                map[string]string      `json:"ortbversion"`
 }
 
 type multiBidSpec struct {
@@ -5539,9 +5572,10 @@ type bidderSeatBid struct {
 // bidderBid is basically a subset of entities.PbsOrtbBid from exchange/bidder.go.
 // See the comment on bidderSeatBid for more info.
 type bidderBid struct {
-	Bid  *openrtb2.Bid                 `json:"ortbBid,omitempty"`
-	Type string                        `json:"bidType,omitempty"`
-	Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"`
+	Bid      *openrtb2.Bid                  `json:"ortbBid,omitempty"`
+	Type     string                         `json:"bidType,omitempty"`
+	BidVideo *openrtb_ext.ExtBidPrebidVideo `json:"bidVideo,omitempty"`
+	Meta     *openrtb_ext.ExtBidPrebidMeta  `json:"bidMeta,omitempty"`
 }
 
 type mockIdFetcher map[string]string
@@ -5588,6 +5622,7 @@ func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderR
 						bids[i] = &entities.PbsOrtbBid{
 							OriginalBidCPM: mockSeatBid.Bids[i].Bid.Price,
 							Bid:            mockSeatBid.Bids[i].Bid,
+							BidVideo:       mockSeatBid.Bids[i].BidVideo,
 							BidType:        openrtb_ext.BidType(mockSeatBid.Bids[i].Type),
 							BidMeta:        mockSeatBid.Bids[i].Meta,
 						}
@@ -5770,6 +5805,24 @@ func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ
 	return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error)
 }
 
+func parseRequestAliases(r openrtb2.BidRequest) (map[string]string, error) {
+	if len(r.Ext) == 0 {
+		return nil, nil
+	}
+
+	ext := struct {
+		Prebid struct {
+			Aliases map[string]string `json:"aliases"`
+		} `json:"prebid"`
+	}{}
+
+	if err := jsonutil.Unmarshal(r.Ext, &ext); err != nil {
+		return nil, err
+	}
+
+	return ext.Prebid.Aliases, nil
+}
+
 func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, error) {
 	bidRequest := req.BidRequest
 	imp := bidRequest.Imp[0]
@@ -6011,7 +6064,7 @@ func TestSelectNewDuration(t *testing.T) {
 func TestSetSeatNonBid(t *testing.T) {
 	type args struct {
 		bidResponseExt *openrtb_ext.ExtBidResponse
-		seatNonBids    nonBids
+		seatNonBids    SeatNonBidBuilder
 	}
 	tests := []struct {
 		name string
@@ -6020,12 +6073,12 @@ func TestSetSeatNonBid(t *testing.T) {
 	}{
 		{
 			name: "empty-seatNonBidsMap",
-			args: args{seatNonBids: nonBids{}, bidResponseExt: nil},
+			args: args{seatNonBids: SeatNonBidBuilder{}, bidResponseExt: nil},
 			want: nil,
 		},
 		{
 			name: "nil-bidResponseExt",
-			args: args{seatNonBids: nonBids{seatNonBidsMap: map[string][]openrtb_ext.NonBid{"key": nil}}, bidResponseExt: nil},
+			args: args{seatNonBids: SeatNonBidBuilder{"key": nil}, bidResponseExt: nil},
 			want: &openrtb_ext.ExtBidResponse{
 				Prebid: &openrtb_ext.ExtResponsePrebid{
 					SeatNonBid: []openrtb_ext.SeatNonBid{{
@@ -6297,3 +6350,11 @@ func TestBidsToUpdate(t *testing.T) {
 		})
 	}
 }
+
+type mockRequestValidator struct {
+	errors []error
+}
+
+func (mrv *mockRequestValidator) ValidateImp(imp *openrtb_ext.ImpWrapper, cfg ortb.ValidationConfig, index int, aliases map[string]string, hasStoredResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) []error {
+	return mrv.errors
+}
diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json
index 14f1181e96a..cd5061001d4 100644
--- a/exchange/exchangetest/append-bidder-names.json
+++ b/exchange/exchangetest/append-bidder-names.json
@@ -149,10 +149,14 @@
                                         "hb_cache_path_appnex": "/pbcache/endpoint",
                                         "hb_pb": "0.20",
                                         "hb_pb_appnexus": "0.20",
-                                        "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus",
-                                        "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus",
+                                        "hb_pb_cat_dur": "0.20_VideoGames_30s_appnexus",
+                                        "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s_appnexus",
                                         "hb_size": "200x250",
                                         "hb_size_appnexus": "200x250"
+                                    },
+                                    "video": {
+                                        "duration": 30,
+                                        "primary_category": ""
                                     }
                                 }
                             }
diff --git a/exchange/exchangetest/ccpa-featureflag-on.json b/exchange/exchangetest/ccpa-featureflag-on.json
index 0c452098da8..c6d37ed91e4 100644
--- a/exchange/exchangetest/ccpa-featureflag-on.json
+++ b/exchange/exchangetest/ccpa-featureflag-on.json
@@ -1,5 +1,8 @@
 {
     "enforceCcpa": true,
+    "ortbversion": {
+        "appnexus":"2.6"
+    },
     "incomingRequest": {
         "ortbRequest": {
             "id": "some-request-id",
@@ -26,9 +29,7 @@
                 }
             ],
             "regs": {
-                "ext": {
-                    "us_privacy": "1-Y-"
-                }
+                "us_privacy": "1-Y-"
             },
             "user": {
                 "buyeruid": "some-buyer-id"
@@ -59,9 +60,7 @@
                         }
                     ],
                     "regs": {
-                        "ext": {
-                            "us_privacy": "1-Y-"
-                        }
+                        "us_privacy": "1-Y-"
                     },
                     "user": {}
                 }
diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json
index 5c0e029c975..8cd095c7bc4 100644
--- a/exchange/exchangetest/debuglog_disabled.json
+++ b/exchange/exchangetest/debuglog_disabled.json
@@ -158,10 +158,14 @@
                     "hb_cache_path_appnex": "/pbcache/endpoint",
                     "hb_pb": "0.20",
                     "hb_pb_appnexus": "0.20",
-                    "hb_pb_cat_dur": "0.20_VideoGames_0s",
-                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                    "hb_pb_cat_dur": "0.20_VideoGames_30s",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s",
                     "hb_size": "200x250",
                     "hb_size_appnexus": "200x250"
+                  },
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
                   }
                 }
               }
diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json
index 11a63f5db2e..6580dc8ad18 100644
--- a/exchange/exchangetest/debuglog_enabled.json
+++ b/exchange/exchangetest/debuglog_enabled.json
@@ -160,10 +160,14 @@
                     "hb_cache_path_appnex": "/pbcache/endpoint",
                     "hb_pb": "0.20",
                     "hb_pb_appnexus": "0.20",
-                    "hb_pb_cat_dur": "0.20_VideoGames_0s",
-                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                    "hb_pb_cat_dur": "0.20_VideoGames_30s",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s",
                     "hb_size": "200x250",
                     "hb_size_appnexus": "200x250"
+                  },
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
                   }
                 }
               }
diff --git a/exchange/exchangetest/eidpermissions-denied.json b/exchange/exchangetest/eidpermissions-denied.json
index 72431595fe3..4f6a25e375d 100644
--- a/exchange/exchangetest/eidpermissions-denied.json
+++ b/exchange/exchangetest/eidpermissions-denied.json
@@ -6,18 +6,16 @@
                 "page": "test.somepage.com"
             },
             "user": {
-                "ext": {
-                    "eids": [
-                        {
-                            "source": "source1",
-                            "uids": [
-                                {
-                                    "id": "id1"
-                                }
-                            ]
-                        }
-                    ]
-                }
+                "eids": [
+                    {
+                        "source": "source1",
+                        "uids": [
+                            {
+                                "id": "id1"
+                            }
+                        ]
+                    }
+                ]
             },
             "ext": {
                 "prebid": {
diff --git a/exchange/exchangetest/firstpartydata-multibidder-user-eids.json b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json
new file mode 100644
index 00000000000..f16907dcdb2
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-multibidder-user-eids.json
@@ -0,0 +1,239 @@
+{
+    "requestType": "openrtb2-web",
+    "ortbversion": {
+        "appnexus":"2.6",
+        "rubicon":"2.6"
+    },
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "user": {
+                "id": "reqUserID",
+                "keywords": "userKeyword!",
+                "eids": [
+                    {
+                        "source": "reqeid1",
+                        "uids": [
+                            {
+                                "id": "reqeiduid1"
+                            }
+                        ]
+                    },
+                    {
+                        "source": "reqieid2",
+                        "uids": [
+                            {
+                                "id": "reqeiduid2"
+                            }
+                        ]
+                    }
+                ]
+            },
+            "imp": [
+                {
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                },
+                                "rubicon": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    }
+                }
+            ],
+            "ext": {
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [
+                            {
+                                "source": "reqeid1",
+                                "bidders": [
+                                    "rubicon"
+                                ]
+                            }
+                        ],
+                        "bidders": [
+                            "appnexus",
+                            "rubicon"
+                        ]
+                    },
+                    "bidderconfig": [
+                        {
+                            "bidders": [
+                                "appnexus"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "site": {
+                                        "domain": "fpd_appnexus_site_domain",
+                                        "page": "fpd_appnexus_site_page"
+                                    }
+                                }
+                            }
+                        },
+                        {
+                            "bidders": [
+                                "rubicon"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "user": {
+                                        "id": "fpdSiteId!4",
+                                        "yob": 2000,
+                                        "keywords": "fpd keywords",
+                                        "eids": [
+                                            {
+                                                "source": "fpdeid1",
+                                                "uids": [
+                                                    {
+                                                        "id": "fpdeiduid1"
+                                                    }
+                                                ]
+                                            },
+                                            {
+                                                "source": "fpdeid2",
+                                                "uids": [
+                                                    {
+                                                        "id": "fpdeiduid1"
+                                                    }
+                                                ]
+                                            }
+                                        ]
+                                    }
+                                }
+                            }
+                        }
+                    ]
+                }
+            }
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "fpd_appnexus_site_domain",
+                        "page": "fpd_appnexus_site_page"
+                    },
+                    "user": {
+                        "id": "reqUserID",
+                        "keywords": "userKeyword!",
+                        "eids": [
+                            {
+                                "source": "reqieid2",
+                                "uids": [
+                                    {
+                                        "id": "reqeiduid2"
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "id": "fpdSiteId!4",
+                        "yob": 2000,
+                        "keywords": "fpd keywords",
+                        "eids": [
+                            {
+                                "source": "fpdeid1",
+                                "uids": [
+                                    {
+                                        "id": "fpdeiduid1"
+                                    }
+                                ]
+                            },
+                            {
+                                "source": "fpdeid2",
+                                "uids": [
+                                    {
+                                        "id": "fpdeiduid1"
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json
new file mode 100644
index 00000000000..2c3b47dc76e
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-user-eids-req-user-nil.json
@@ -0,0 +1,203 @@
+{
+    "requestType": "openrtb2-web",
+    "ortbversion": {
+        "appnexus":"2.6",
+        "rubicon":"2.6"
+    },
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [
+                {
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                },
+                                "rubicon": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    }
+                }
+            ],
+            "ext": {
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [
+                            {
+                                "source": "reqeid1",
+                                "bidders": [
+                                    "rubicon"
+                                ]
+                            }
+                        ],
+                        "bidders": [
+                            "appnexus",
+                            "rubicon"
+                        ]
+                    },
+                    "bidderconfig": [
+                        {
+                            "bidders": [
+                                "appnexus"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "site": {
+                                        "domain": "fpd_appnexus_site_domain",
+                                        "page": "fpd_appnexus_site_page"
+                                    }
+                                }
+                            }
+                        },
+                        {
+                            "bidders": [
+                                "rubicon"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "user": {
+                                        "id": "fpdSiteId!4",
+                                        "yob": 2000,
+                                        "keywords": "fpd keywords",
+                                        "eids": [
+                                            {
+                                                "source": "fpdeid1",
+                                                "uids": [
+                                                    {
+                                                        "id": "fpdeiduid1"
+                                                    }
+                                                ]
+                                            },
+                                            {
+                                                "source": "fpdeid2",
+                                                "uids": [
+                                                    {
+                                                        "id": "fpdeiduid1"
+                                                    }
+                                                ]
+                                            }
+                                        ]
+                                    }
+                                }
+                            }
+                        }
+                    ]
+                }
+            }
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "fpd_appnexus_site_domain",
+                        "page": "fpd_appnexus_site_page"
+                    }
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "id": "fpdSiteId!4",
+                        "yob": 2000,
+                        "keywords": "fpd keywords",
+                        "eids": [
+                            {
+                                "source": "fpdeid1",
+                                "uids": [
+                                    {
+                                        "id": "fpdeiduid1"
+                                    }
+                                ]
+                            },
+                            {
+                                "source": "fpdeid2",
+                                "uids": [
+                                    {
+                                        "id": "fpdeiduid1"
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json
new file mode 100644
index 00000000000..3ea8382176c
--- /dev/null
+++ b/exchange/exchangetest/firstpartydata-user-nileids-req-user-nil.json
@@ -0,0 +1,163 @@
+{
+    "requestType": "openrtb2-web",
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "imp": [
+                {
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                },
+                                "rubicon": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    }
+                }
+            ],
+            "ext": {
+                "prebid": {
+                    "data": {
+                        "eidpermissions": [
+                            {
+                                "source": "reqeid1",
+                                "bidders": [
+                                    "rubicon"
+                                ]
+                            }
+                        ],
+                        "bidders": [
+                            "appnexus",
+                            "rubicon"
+                        ]
+                    },
+                    "bidderconfig": [
+                        {
+                            "bidders": [
+                                "appnexus"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "site": {
+                                        "domain": "fpd_appnexus_site_domain",
+                                        "page": "fpd_appnexus_site_page"
+                                    }
+                                }
+                            }
+                        },
+                        {
+                            "bidders": [
+                                "rubicon"
+                            ],
+                            "config": {
+                                "ortb2": {
+                                    "user": {
+                                        "id": "fpdSiteId!4",
+                                        "yob": 2000,
+                                        "keywords": "fpd keywords"
+                                    }
+                                }
+                            }
+                        }
+                    ]
+                }
+            }
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "domain": "fpd_appnexus_site_domain",
+                        "page": "fpd_appnexus_site_page"
+                    }
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    ],
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "id": "fpdSiteId!4",
+                        "yob": 2000,
+                        "keywords": "fpd keywords"
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/generate-bid-id-error.json b/exchange/exchangetest/generate-bid-id-error.json
new file mode 100644
index 00000000000..3536f38b3d1
--- /dev/null
+++ b/exchange/exchangetest/generate-bid-id-error.json
@@ -0,0 +1,203 @@
+{
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": true
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "my-imp-id",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "prebid": {
+              "bidder": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          }
+        }
+      ],
+      "test": 1,
+      "ext": {
+        "prebid": {
+          "targeting": {
+            "includebrandcategory": {
+              "primaryadserver": 1,
+              "publisher": "",
+              "withcategory": true
+            },
+            "pricegranularity": {
+              "precision": 2,
+              "ranges": [
+                {
+                  "min": 0,
+                  "max": 20,
+                  "increment": 0.1
+                }
+              ]
+            },
+            "includewinners": true,
+            "includebidderkeys": true,
+            "appendbiddernames": true
+          }
+        }
+      }
+    },
+    "usersyncs": {
+      "appnexus": "123"
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBids": [
+          {
+            "pbsBids": [
+              {
+                "ortbBid": {
+                  "id": "apn-bid",
+                  "impid": "my-imp-id",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": [
+                    "IAB1-1"
+                  ]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30,
+                  "PrimaryCategory": ""
+                }
+              }
+            ],
+            "seat": "appnexus"
+          }
+        ]
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "my-imp-id",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "origbidcpm": 0.3,
+                "prebid": {
+                  "meta": {
+                  },
+                  "type": "video",
+                  "targeting": {
+                    "hb_bidder": "appnexus",
+                    "hb_bidder_appnexus": "appnexus",
+                    "hb_cache_host": "www.pbcserver.com",
+                    "hb_cache_host_appnex": "www.pbcserver.com",
+                    "hb_cache_path": "/pbcache/endpoint",
+                    "hb_cache_path_appnex": "/pbcache/endpoint",
+                    "hb_pb": "0.20",
+                    "hb_pb_appnexus": "0.20",
+                    "hb_pb_cat_dur": "0.20_VideoGames_30s_appnexus",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s_appnexus",
+                    "hb_size": "200x250",
+                    "hb_size_appnexus": "200x250"
+                  },
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "my-imp-id",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "prebid": {
+                  "bidder": {
+                    "appnexus": {
+                      "placementId": 1
+                    }
+                  }
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          },
+          "test": 1,
+          "ext": {
+            "prebid": {
+              "targeting": {
+                "includebrandcategory": {
+                  "primaryadserver": 1,
+                  "publisher": "",
+                  "withcategory": true
+                },
+                "pricegranularity": {
+                  "precision": 2,
+                  "ranges": [
+                    {
+                      "min": 0,
+                      "max": 20,
+                      "increment": 0.1
+                    }
+                  ]
+                },
+                "includewinners": true,
+                "includebidderkeys": true,
+                "appendbiddernames": true
+              }
+            }
+          }
+        }
+      },
+      "errors": {
+        "prebid": [
+          {
+            "code": 999,
+            "message": "Error generating bid.ext.prebid.bidid"
+          }
+        ]
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/generate-bid-id-many.json b/exchange/exchangetest/generate-bid-id-many.json
new file mode 100644
index 00000000000..ecdefbaa71b
--- /dev/null
+++ b/exchange/exchangetest/generate-bid-id-many.json
@@ -0,0 +1,170 @@
+{
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": false
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "imp-id-1",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "prebid": {
+              "bidder": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          }
+        }
+      ]
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBids": [
+          {
+            "pbsBids": [
+              {
+                "ortbBid": {
+                  "id": "apn-bid-1",
+                  "impid": "imp-id-1",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": [
+                    "IAB1-1"
+                  ]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30
+                }
+              },
+              {
+                "ortbBid": {
+                  "id": "apn-bid-2",
+                  "impid": "imp-id-1",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": [
+                    "IAB1-1"
+                  ]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30,
+                  "PrimaryCategory": ""
+                }
+              }
+            ],
+            "seat": "appnexus"
+          }
+        ]
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid-1",
+              "impid": "imp-id-1",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "origbidcpm": 0.3,
+                "prebid": {
+                  "meta": {
+                  },
+                  "bidid": "bid-appnexus-1",
+                  "type": "video",
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
+                  }
+                }
+              }
+            },
+            {
+              "id": "apn-bid-2",
+              "impid": "imp-id-1",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "origbidcpm": 0.3,
+                "prebid": {
+                  "meta": {
+                  },
+                  "bidid": "bid-appnexus-2",
+                  "type": "video",
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "prebid": {
+                  "bidder": {
+                    "appnexus": {
+                      "placementId": 1
+                    }
+                  }
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/generate-bid-id-one.json b/exchange/exchangetest/generate-bid-id-one.json
new file mode 100644
index 00000000000..ccd6abd05f2
--- /dev/null
+++ b/exchange/exchangetest/generate-bid-id-one.json
@@ -0,0 +1,129 @@
+{
+  "bidIDGenerator": {
+    "generateBidID": true,
+    "returnError": false
+  },
+  "incomingRequest": {
+    "ortbRequest": {
+      "id": "some-request-id",
+      "site": {
+        "page": "test.somepage.com"
+      },
+      "imp": [
+        {
+          "id": "imp-id-1",
+          "video": {
+            "mimes": [
+              "video/mp4"
+            ]
+          },
+          "ext": {
+            "prebid": {
+              "bidder": {
+                "appnexus": {
+                  "placementId": 1
+                }
+              }
+            }
+          }
+        }
+      ]
+    }
+  },
+  "outgoingRequests": {
+    "appnexus": {
+      "mockResponse": {
+        "pbsSeatBids": [
+          {
+            "pbsBids": [
+              {
+                "ortbBid": {
+                  "id": "apn-bid",
+                  "impid": "imp-id-1",
+                  "price": 0.3,
+                  "w": 200,
+                  "h": 250,
+                  "crid": "creative-1",
+                  "cat": [
+                    "IAB1-1"
+                  ]
+                },
+                "bidType": "video",
+                "bidVideo": {
+                  "duration": 30,
+                  "primary_category": ""
+                }
+              }
+            ],
+            "seat": "appnexus"
+          }
+        ]
+      }
+    }
+  },
+  "response": {
+    "bids": {
+      "id": "some-request-id",
+      "seatbid": [
+        {
+          "seat": "appnexus",
+          "bid": [
+            {
+              "id": "apn-bid",
+              "impid": "imp-id-1",
+              "price": 0.3,
+              "w": 200,
+              "h": 250,
+              "crid": "creative-1",
+              "cat": [
+                "IAB1-1"
+              ],
+              "ext": {
+                "origbidcpm": 0.3,
+                "prebid": {
+                  "meta": {
+                  },
+                  "bidid": "bid-appnexus-1",
+                  "type": "video",
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
+                  }
+                }
+              }
+            }
+          ]
+        }
+      ]
+    },
+    "ext": {
+      "debug": {
+        "resolvedrequest": {
+          "id": "some-request-id",
+          "imp": [
+            {
+              "id": "imp-id-1",
+              "video": {
+                "mimes": [
+                  "video/mp4"
+                ]
+              },
+              "ext": {
+                "prebid": {
+                  "bidder": {
+                    "appnexus": {
+                      "placementId": 1
+                    }
+                  }
+                }
+              }
+            }
+          ],
+          "site": {
+            "page": "test.somepage.com"
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/include-brand-category.json b/exchange/exchangetest/include-brand-category.json
index c0904448375..d3b5e87a8f8 100644
--- a/exchange/exchangetest/include-brand-category.json
+++ b/exchange/exchangetest/include-brand-category.json
@@ -147,10 +147,14 @@
                     "hb_cache_path_appnex": "/pbcache/endpoint",
                     "hb_pb": "0.20",
                     "hb_pb_appnexus": "0.20",
-                    "hb_pb_cat_dur": "0.20_VideoGames_0s",
-                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s",
+                    "hb_pb_cat_dur": "0.20_VideoGames_30s",
+                    "hb_pb_cat_dur_appnex": "0.20_VideoGames_30s",
                     "hb_size": "200x250",
                     "hb_size_appnexus": "200x250"
+                  },
+                  "video": {
+                    "duration": 30,
+                    "primary_category": ""
                   }
                 }
               }
diff --git a/exchange/exchangetest/multi-bids-different-ortb-versions.json b/exchange/exchangetest/multi-bids-different-ortb-versions.json
new file mode 100644
index 00000000000..37e7fc582c7
--- /dev/null
+++ b/exchange/exchangetest/multi-bids-different-ortb-versions.json
@@ -0,0 +1,276 @@
+{
+    "requestType": "openrtb2-web",
+    "ortbversion": {
+        "appnexus":"2.6",
+        "rubicon":"2.5"
+    },
+    "incomingRequest": {
+        "ortbRequest": {
+            "id": "some-request-id",
+            "site": {
+                "page": "test.somepage.com"
+            },
+            "user": {
+                "id": "my-user-id",
+                "buyeruid": "my-buyer-uid",
+                "geo": {
+                    "lat": 123.456,
+                    "lon": 678.90,
+                    "zip": "90210"
+                },
+                "consent": "AAAA",
+                "eids": [
+                    {
+                        "source": "source",
+                        "uids": [
+                            {
+                                "id": "1",
+                                "atype": 1,
+                                "ext": {}
+                            },
+                            {
+                                "id": "1",
+                                "atype": 1,
+                                "ext": {}
+                            }
+                        ],
+                        "ext": {}
+                    }
+                ],
+                "ext": {}
+            },
+            "imp": [
+                {
+                    "rwdd": 1,
+                    "id": "some-imp-id",
+                    "banner": {
+                        "format": [
+                            {
+                                "w": 600,
+                                "h": 500
+                            },
+                            {
+                                "w": 300,
+                                "h": 600
+                            }
+                        ]
+                    },
+                    "ext": {
+                        "prebid": {
+                            "bidder": {
+                                "appnexus": {
+                                    "placementId": 1
+                                },
+                                "rubicon": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                }
+                            }
+                        }
+                    }
+                }
+            ],
+            "source": {
+                "fd": 1,
+                "tid": "abc123",
+                "pchain": "tag_placement",
+                "schain": {
+                    "complete": 1,
+                    "nodes": [
+                        {
+                            "asi": "asi",
+                            "sid": "sid",
+                            "rid": "rid",
+                            "ext": {}
+                        }
+                    ],
+                    "ver": "ver",
+                    "ext": {}
+                },
+                "ext": {}
+            },
+            "regs": {
+                "coppa": 1,
+                "gdpr": 1,
+                "us_privacy": "1YYY",
+                "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN",
+                "gppsid": [
+                    6
+                ]
+            }
+        }
+    },
+    "outgoingRequests": {
+        "appnexus": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "geo": {},
+                        "consent": "AAAA",
+                        "eids": [
+                            {
+                                "source": "source",
+                                "uids": [
+                                    {
+                                        "id": "1",
+                                        "atype": 1,
+                                        "ext": {}
+                                    },
+                                    {
+                                        "id": "1",
+                                        "atype": 1,
+                                        "ext": {}
+                                    }
+                                ],
+                                "ext": {}
+                            }
+                        ],
+                        "ext": {}
+                    },
+                    "imp": [
+                        {
+                            "rwdd": 1,
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "placementId": 1
+                                }
+                            }
+                        }
+                    ],
+                    "source": {
+                        "fd": 1,
+                        "tid": "abc123",
+                        "pchain": "tag_placement",
+                        "schain": {
+                            "complete": 1,
+                            "nodes": [
+                                {
+                                    "asi": "asi",
+                                    "sid": "sid",
+                                    "rid": "rid",
+                                    "ext": {}
+                                }
+                            ],
+                            "ver": "ver",
+                            "ext": {}
+                        },
+                        "ext": {}
+                    },
+                    "regs": {
+                        "coppa": 1,
+                        "gdpr": 1,
+                        "us_privacy": "1YYY",
+                        "gpp": "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN"
+                    }
+                }
+            }
+        },
+        "rubicon": {
+            "expectRequest": {
+                "ortbRequest": {
+                    "id": "some-request-id",
+                    "site": {
+                        "page": "test.somepage.com"
+                    },
+                    "user": {
+                        "geo": {},
+                        "ext": {
+                            "consent": "AAAA",
+                            "eids": [
+                                {
+                                    "source": "source",
+                                    "uids": [
+                                        {
+                                            "id": "1",
+                                            "atype": 1,
+                                            "ext": {}
+                                        },
+                                        {
+                                            "id": "1",
+                                            "atype": 1,
+                                            "ext": {}
+                                        }
+                                    ],
+                                    "ext": {}
+                                }
+                            ]
+                        }
+                    },
+                    "imp": [
+                        {
+                            "id": "some-imp-id",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 600,
+                                        "h": 500
+                                    },
+                                    {
+                                        "w": 300,
+                                        "h": 600
+                                    }
+                                ]
+                            },
+                            "ext": {
+                                "bidder": {
+                                    "accountId": 1,
+                                    "siteId": 2,
+                                    "zoneId": 3
+                                },
+                                "prebid": {
+                                    "is_rewarded_inventory":1
+                                }
+                            }
+                        }
+                    ],
+                    "source": {
+                        "fd": 1,
+                        "tid": "abc123",
+                        "pchain": "tag_placement",
+                        "ext": {
+                            "schain": {
+                                "complete": 1,
+                                "nodes": [
+                                    {
+                                        "asi": "asi",
+                                        "sid": "sid",
+                                        "rid": "rid",
+                                        "ext": {}
+                                    }
+                                ],
+                                "ver": "ver",
+                                "ext": {}
+                            }
+                        }
+                    },
+                    "regs": {
+                        "coppa": 1,
+                        "gpp":"DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN",
+                        "ext": {
+                            "gdpr": 1,
+                            "us_privacy": "1YYY"
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/exchange/exchangetest/schain-host-and-request.json b/exchange/exchangetest/schain-host-and-request.json
index 5bfcd1c3527..0552bb09b30 100644
--- a/exchange/exchangetest/schain-host-and-request.json
+++ b/exchange/exchangetest/schain-host-and-request.json
@@ -1,5 +1,8 @@
 {
     "host_schain_flag": true,
+    "ortbversion": {
+        "appnexus":"2.6"
+    },
     "incomingRequest": {
         "ortbRequest": {
             "id": "some-request-id",
@@ -84,25 +87,23 @@
                         }
                     },
                     "source": {
-                        "ext": {
-                            "schain": {
-                                "complete": 1,
-                                "nodes": [
-                                    {
-                                        "asi": "whatever.com",
-                                        "sid": "1234",
-                                        "rid": "123-456-7890",
-                                        "hp": 1
-                                    },
-                                    {
-                                        "asi": "pbshostcompany.com",
-                                        "sid": "00001",
-                                        "rid": "BidRequest",
-                                        "hp": 1
-                                    }
-                                ],
-                                "ver": "2.0"
-                            }
+                        "schain": {
+                            "complete": 1,
+                            "nodes": [
+                                {
+                                    "asi": "whatever.com",
+                                    "sid": "1234",
+                                    "rid": "123-456-7890",
+                                    "hp": 1
+                                },
+                                {
+                                    "asi": "pbshostcompany.com",
+                                    "sid": "00001",
+                                    "rid": "BidRequest",
+                                    "hp": 1
+                                }
+                            ],
+                            "ver": "2.0"
                         }
                     }
                 },
diff --git a/exchange/exchangetest/schain-host-only.json b/exchange/exchangetest/schain-host-only.json
index 0cc077e2fdb..89fb70c3676 100644
--- a/exchange/exchangetest/schain-host-only.json
+++ b/exchange/exchangetest/schain-host-only.json
@@ -1,5 +1,8 @@
 {
     "host_schain_flag": true,
+    "ortbversion": {
+        "appnexus":"2.6"
+    },
     "incomingRequest": {
         "ortbRequest": {
             "id": "some-request-id",
@@ -64,19 +67,17 @@
                         }
                     },
                     "source": {
-                        "ext": {
-                            "schain": {
-                                "complete": 0,
-                                "nodes": [
-                                    {
-                                        "asi": "pbshostcompany.com",
-                                        "sid": "00001",
-                                        "rid": "BidRequest",
-                                        "hp": 1
-                                    }
-                                ],
-                                "ver": "1.0"
-                            }
+                        "schain": {
+                            "complete": 0,
+                            "nodes": [
+                                {
+                                    "asi": "pbshostcompany.com",
+                                    "sid": "00001",
+                                    "rid": "BidRequest",
+                                    "hp": 1
+                                }
+                            ],
+                            "ver": "1.0"
                         }
                     }
                 },
diff --git a/exchange/gdpr.go b/exchange/gdpr.go
index 52fb860f5df..d548c8d7019 100644
--- a/exchange/gdpr.go
+++ b/exchange/gdpr.go
@@ -16,11 +16,11 @@ func getGDPR(req *openrtb_ext.RequestWrapper) (gdpr.Signal, error) {
 		}
 		return gdpr.SignalNo, nil
 	}
-	re, err := req.GetRegExt()
-	if re == nil || re.GetGDPR() == nil || err != nil {
-		return gdpr.SignalAmbiguous, err
+	if req.Regs != nil && req.Regs.GDPR != nil {
+		return gdpr.IntSignalParse(int(*req.Regs.GDPR))
 	}
-	return gdpr.Signal(*re.GetGDPR()), nil
+	return gdpr.SignalAmbiguous, nil
+
 }
 
 // getConsent will pull the consent string from an openrtb request
@@ -29,9 +29,8 @@ func getConsent(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (conse
 		consent = gpp.Sections[i].GetValue()
 		return
 	}
-	ue, err := req.GetUserExt()
-	if ue == nil || ue.GetConsent() == nil || err != nil {
-		return
+	if req.User != nil {
+		return req.User.Consent, nil
 	}
-	return *ue.GetConsent(), nil
+	return
 }
diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go
index c73112be6f3..a775e0b1fa6 100644
--- a/exchange/gdpr_test.go
+++ b/exchange/gdpr_test.go
@@ -1,7 +1,6 @@
 package exchange
 
 import (
-	"encoding/json"
 	"testing"
 
 	gpplib "github.com/prebid/go-gpp"
@@ -9,6 +8,7 @@ import (
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v2/gdpr"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/util/ptrutil"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -21,17 +21,17 @@ func TestGetGDPR(t *testing.T) {
 	}{
 		{
 			description: "Regs Ext GDPR = 0",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)},
+			giveRegs:    &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0)},
 			wantGDPR:    gdpr.SignalNo,
 		},
 		{
 			description: "Regs Ext GDPR = 1",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)},
+			giveRegs:    &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1)},
 			wantGDPR:    gdpr.SignalYes,
 		},
 		{
 			description: "Regs Ext GDPR = null",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`)},
+			giveRegs:    &openrtb2.Regs{GDPR: nil},
 			wantGDPR:    gdpr.SignalAmbiguous,
 		},
 		{
@@ -39,30 +39,19 @@ func TestGetGDPR(t *testing.T) {
 			giveRegs:    nil,
 			wantGDPR:    gdpr.SignalAmbiguous,
 		},
-		{
-			description: "Regs Ext is nil",
-			giveRegs:    &openrtb2.Regs{Ext: nil},
-			wantGDPR:    gdpr.SignalAmbiguous,
-		},
-		{
-			description: "JSON unmarshal error",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"`)},
-			wantGDPR:    gdpr.SignalAmbiguous,
-			wantError:   true,
-		},
 		{
 			description: "Regs Ext GDPR = null, GPPSID has tcf2",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`), GPPSID: []int8{2}},
+			giveRegs:    &openrtb2.Regs{GDPR: nil, GPPSID: []int8{2}},
 			wantGDPR:    gdpr.SignalYes,
 		},
 		{
 			description: "Regs Ext GDPR = 1, GPPSID has uspv1",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`), GPPSID: []int8{6}},
+			giveRegs:    &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](1), GPPSID: []int8{6}},
 			wantGDPR:    gdpr.SignalNo,
 		},
 		{
 			description: "Regs Ext GDPR = 0, GPPSID has tcf2",
-			giveRegs:    &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`), GPPSID: []int8{2}},
+			giveRegs:    &openrtb2.Regs{GDPR: ptrutil.ToPtr[int8](0), GPPSID: []int8{2}},
 			wantGDPR:    gdpr.SignalYes,
 		},
 		{
@@ -105,18 +94,13 @@ func TestGetConsent(t *testing.T) {
 		wantError   bool
 	}{
 		{
-			description: "User Ext Consent is not empty",
-			giveUser:    &openrtb2.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)},
+			description: "User Consent is not empty",
+			giveUser:    &openrtb2.User{Consent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"},
 			wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA",
 		},
 		{
-			description: "User Ext Consent is empty",
-			giveUser:    &openrtb2.User{Ext: json.RawMessage(`{"consent": ""}`)},
-			wantConsent: "",
-		},
-		{
-			description: "User Ext is nil",
-			giveUser:    &openrtb2.User{Ext: nil},
+			description: "User Consent is empty",
+			giveUser:    &openrtb2.User{Consent: ""},
 			wantConsent: "",
 		},
 		{
@@ -125,26 +109,20 @@ func TestGetConsent(t *testing.T) {
 			wantConsent: "",
 		},
 		{
-			description: "JSON unmarshal error",
-			giveUser:    &openrtb2.User{Ext: json.RawMessage(`{`)},
-			wantConsent: "",
-			wantError:   true,
-		},
-		{
-			description: "User Ext is nil, GPP has no GDPR",
-			giveUser:    &openrtb2.User{Ext: nil},
+			description: "User is nil, GPP has no GDPR",
+			giveUser:    nil,
 			giveGPP:     gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}},
 			wantConsent: "",
 		},
 		{
-			description: "User Ext is nil, GPP has GDPR",
-			giveUser:    &openrtb2.User{Ext: nil},
+			description: "User is nil, GPP has GDPR",
+			giveUser:    nil,
 			giveGPP:     gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}},
 			wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA",
 		},
 		{
-			description: "User Ext has GDPR, GPP has GDPR",
-			giveUser:    &openrtb2.User{Ext: json.RawMessage(`{"consent": "BSOMECONSENT"}`)},
+			description: "User has GDPR, GPP has GDPR",
+			giveUser:    &openrtb2.User{Consent: "BSOMECONSENT"},
 			giveGPP:     gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{2}, Sections: []gpplib.Section{&tcf1Section}},
 			wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA",
 		},
diff --git a/exchange/non_bid_reason.go b/exchange/non_bid_reason.go
index 2dff2dfbcbf..f12c01a2af9 100644
--- a/exchange/non_bid_reason.go
+++ b/exchange/non_bid_reason.go
@@ -1,27 +1,55 @@
 package exchange
 
+import (
+	"errors"
+	"net"
+	"syscall"
+
+	"github.com/prebid/prebid-server/v2/errortypes"
+)
+
 // SeatNonBid list the reasons why bid was not resulted in positive bid
 // reason could be either No bid, Error, Request rejection or Response rejection
-// Reference:  https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/seat-non-bid.md
-type NonBidReason int
+// Reference:  https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/seat-non-bid.md#list-non-bid-status-codes
+type NonBidReason int64
 
 const (
-	NoBidUnknownError                      NonBidReason = 0 // No Bid - General
+	ErrorGeneral                           NonBidReason = 100 // Error - General
+	ErrorTimeout                           NonBidReason = 101 // Error - Timeout
+	ErrorBidderUnreachable                 NonBidReason = 103 // Error - Bidder Unreachable
 	ResponseRejectedGeneral                NonBidReason = 300
 	ResponseRejectedCategoryMappingInvalid NonBidReason = 303 // Response Rejected - Category Mapping Invalid
 	ResponseRejectedCreativeSizeNotAllowed NonBidReason = 351 // Response Rejected - Invalid Creative (Size Not Allowed)
 	ResponseRejectedCreativeNotSecure      NonBidReason = 352 // Response Rejected - Invalid Creative (Not Secure)
 )
 
-// Ptr returns pointer to own value.
-func (n NonBidReason) Ptr() *NonBidReason {
-	return &n
+func errorToNonBidReason(err error) NonBidReason {
+	switch errortypes.ReadCode(err) {
+	case errortypes.TimeoutErrorCode:
+		return ErrorTimeout
+	default:
+		return ErrorGeneral
+	}
 }
 
-// Val safely dereferences pointer, returning default value (NoBidUnknownError) for nil.
-func (n *NonBidReason) Val() NonBidReason {
-	if n == nil {
-		return NoBidUnknownError
+// httpInfoToNonBidReason determines NoBidReason code (NBR)
+// It will first try to resolve the NBR based on prebid's proprietary error code.
+// If proprietary error code not found then it will try to determine NBR using
+// system call level error code
+func httpInfoToNonBidReason(httpInfo *httpCallInfo) NonBidReason {
+	nonBidReason := errorToNonBidReason(httpInfo.err)
+	if nonBidReason != ErrorGeneral {
+		return nonBidReason
 	}
-	return *n
+	if isBidderUnreachableError(httpInfo) {
+		return ErrorBidderUnreachable
+	}
+	return ErrorGeneral
+}
+
+// isBidderUnreachableError checks if the error is due to connection refused or no such host
+func isBidderUnreachableError(httpInfo *httpCallInfo) bool {
+	var dnsErr *net.DNSError
+	isNoSuchHost := errors.As(httpInfo.err, &dnsErr) && dnsErr.IsNotFound
+	return errors.Is(httpInfo.err, syscall.ECONNREFUSED) || isNoSuchHost
 }
diff --git a/exchange/non_bid_reason_test.go b/exchange/non_bid_reason_test.go
new file mode 100644
index 00000000000..ab5c9b4f957
--- /dev/null
+++ b/exchange/non_bid_reason_test.go
@@ -0,0 +1,65 @@
+package exchange
+
+import (
+	"errors"
+	"net"
+	"syscall"
+	"testing"
+
+	"github.com/prebid/prebid-server/v2/errortypes"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_httpInfoToNonBidReason(t *testing.T) {
+	type args struct {
+		httpInfo *httpCallInfo
+	}
+	tests := []struct {
+		name string
+		args args
+		want NonBidReason
+	}{
+		{
+			name: "error-timeout",
+			args: args{
+				httpInfo: &httpCallInfo{
+					err: &errortypes.Timeout{},
+				},
+			},
+			want: ErrorTimeout,
+		},
+		{
+			name: "error-general",
+			args: args{
+				httpInfo: &httpCallInfo{
+					err: errors.New("some_error"),
+				},
+			},
+			want: ErrorGeneral,
+		},
+		{
+			name: "error-bidderUnreachable",
+			args: args{
+				httpInfo: &httpCallInfo{
+					err: syscall.ECONNREFUSED,
+				},
+			},
+			want: ErrorBidderUnreachable,
+		},
+		{
+			name: "error-biddersUnreachable-no-such-host",
+			args: args{
+				httpInfo: &httpCallInfo{
+					err: &net.DNSError{IsNotFound: true},
+				},
+			},
+			want: ErrorBidderUnreachable,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := httpInfoToNonBidReason(tt.args.httpInfo)
+			assert.Equal(t, tt.want, actual)
+		})
+	}
+}
diff --git a/exchange/seat_non_bids.go b/exchange/seat_non_bids.go
index 78c1b23e3f3..760431ef44f 100644
--- a/exchange/seat_non_bids.go
+++ b/exchange/seat_non_bids.go
@@ -5,22 +5,18 @@ import (
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
 )
 
-type nonBids struct {
-	seatNonBidsMap map[string][]openrtb_ext.NonBid
-}
+type SeatNonBidBuilder map[string][]openrtb_ext.NonBid
 
-// addBid is not thread safe as we are initializing and writing to map
-func (snb *nonBids) addBid(bid *entities.PbsOrtbBid, nonBidReason int, seat string) {
-	if bid == nil || bid.Bid == nil {
+// rejectBid appends a non bid object to the builder based on a bid
+func (b SeatNonBidBuilder) rejectBid(bid *entities.PbsOrtbBid, nonBidReason int, seat string) {
+	if b == nil || bid == nil || bid.Bid == nil {
 		return
 	}
-	if snb.seatNonBidsMap == nil {
-		snb.seatNonBidsMap = make(map[string][]openrtb_ext.NonBid)
-	}
+
 	nonBid := openrtb_ext.NonBid{
 		ImpId:      bid.Bid.ImpID,
 		StatusCode: nonBidReason,
-		Ext: openrtb_ext.NonBidExt{
+		Ext: &openrtb_ext.NonBidExt{
 			Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{
 				Price:          bid.Bid.Price,
 				ADomain:        bid.Bid.ADomain,
@@ -36,16 +32,29 @@ func (snb *nonBids) addBid(bid *entities.PbsOrtbBid, nonBidReason int, seat stri
 			}},
 		},
 	}
-
-	snb.seatNonBidsMap[seat] = append(snb.seatNonBidsMap[seat], nonBid)
+	b[seat] = append(b[seat], nonBid)
 }
 
-func (snb *nonBids) get() []openrtb_ext.SeatNonBid {
-	if snb == nil {
-		return nil
+// rejectImps appends a non bid object to the builder for every specified imp
+func (b SeatNonBidBuilder) rejectImps(impIds []string, nonBidReason NonBidReason, seat string) {
+	nonBids := []openrtb_ext.NonBid{}
+	for _, impId := range impIds {
+		nonBid := openrtb_ext.NonBid{
+			ImpId:      impId,
+			StatusCode: int(nonBidReason),
+		}
+		nonBids = append(nonBids, nonBid)
 	}
-	var seatNonBid []openrtb_ext.SeatNonBid
-	for seat, nonBids := range snb.seatNonBidsMap {
+
+	if len(nonBids) > 0 {
+		b[seat] = append(b[seat], nonBids...)
+	}
+}
+
+// slice transforms the seat non bid map into a slice of SeatNonBid objects representing the non-bids for each seat
+func (b SeatNonBidBuilder) Slice() []openrtb_ext.SeatNonBid {
+	seatNonBid := make([]openrtb_ext.SeatNonBid, 0)
+	for seat, nonBids := range b {
 		seatNonBid = append(seatNonBid, openrtb_ext.SeatNonBid{
 			Seat:   seat,
 			NonBid: nonBids,
@@ -53,3 +62,16 @@ func (snb *nonBids) get() []openrtb_ext.SeatNonBid {
 	}
 	return seatNonBid
 }
+
+// append adds the nonBids from the input nonBids to the current nonBids.
+// This method is not thread safe as we are initializing and writing to map
+func (b SeatNonBidBuilder) append(nonBids ...SeatNonBidBuilder) {
+	if b == nil {
+		return
+	}
+	for _, nonBid := range nonBids {
+		for seat, nonBids := range nonBid {
+			b[seat] = append(b[seat], nonBids...)
+		}
+	}
+}
diff --git a/exchange/seat_non_bids_test.go b/exchange/seat_non_bids_test.go
index 103c0939496..b754f885965 100644
--- a/exchange/seat_non_bids_test.go
+++ b/exchange/seat_non_bids_test.go
@@ -9,9 +9,9 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestSeatNonBidsAdd(t *testing.T) {
+func TestRejectBid(t *testing.T) {
 	type fields struct {
-		seatNonBidsMap map[string][]openrtb_ext.NonBid
+		builder SeatNonBidBuilder
 	}
 	type args struct {
 		bid          *entities.PbsOrtbBid
@@ -22,89 +22,512 @@ func TestSeatNonBidsAdd(t *testing.T) {
 		name   string
 		fields fields
 		args   args
-		want   map[string][]openrtb_ext.NonBid
+		want   SeatNonBidBuilder
 	}{
 		{
-			name:   "nil-seatNonBidsMap",
-			fields: fields{seatNonBidsMap: nil},
-			args:   args{},
-			want:   nil,
+			name: "nil_builder",
+			fields: fields{
+				builder: nil,
+			},
+			args: args{},
+			want: nil,
 		},
 		{
-			name:   "nil-seatNonBidsMap-with-bid-object",
-			fields: fields{seatNonBidsMap: nil},
-			args:   args{bid: &entities.PbsOrtbBid{Bid: &openrtb2.Bid{}}, seat: "bidder1"},
-			want:   sampleSeatNonBidMap("bidder1", 1),
+			name: "nil_pbsortbid",
+			fields: fields{
+				builder: SeatNonBidBuilder{},
+			},
+			args: args{
+				bid: nil,
+			},
+			want: SeatNonBidBuilder{},
 		},
 		{
-			name:   "multiple-nonbids-for-same-seat",
-			fields: fields{seatNonBidsMap: sampleSeatNonBidMap("bidder2", 1)},
-			args:   args{bid: &entities.PbsOrtbBid{Bid: &openrtb2.Bid{}}, seat: "bidder2"},
-			want:   sampleSeatNonBidMap("bidder2", 2),
+			name: "nil_bid",
+			fields: fields{
+				builder: SeatNonBidBuilder{},
+			},
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: nil,
+				},
+			},
+			want: SeatNonBidBuilder{},
+		},
+		{
+			name: "append_nonbids_new_seat",
+			fields: fields{
+				builder: SeatNonBidBuilder{},
+			},
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: &openrtb2.Bid{
+						ImpID: "Imp1",
+						Price: 10,
+					},
+				},
+				nonBidReason: int(ErrorGeneral),
+				seat:         "seat1",
+			},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "Imp1",
+						StatusCode: int(ErrorGeneral),
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{
+									Price: 10,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "append_nonbids_for_different_seat",
+			fields: fields{
+				builder: SeatNonBidBuilder{
+					"seat1": []openrtb_ext.NonBid{
+						{
+							ImpId:      "Imp1",
+							StatusCode: int(ErrorGeneral),
+						},
+					},
+				},
+			},
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: &openrtb2.Bid{
+						ImpID: "Imp2",
+						Price: 10,
+					},
+				},
+				nonBidReason: int(ErrorGeneral),
+				seat:         "seat2",
+			},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "Imp1",
+						StatusCode: int(ErrorGeneral),
+					},
+				},
+				"seat2": []openrtb_ext.NonBid{
+					{
+						ImpId:      "Imp2",
+						StatusCode: int(ErrorGeneral),
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{
+									Price: 10,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "append_nonbids_for_existing_seat",
+			fields: fields{
+				builder: SeatNonBidBuilder{
+					"seat1": []openrtb_ext.NonBid{
+						{
+							ImpId:      "Imp1",
+							StatusCode: int(ErrorGeneral),
+						},
+					},
+				},
+			},
+			args: args{
+				bid: &entities.PbsOrtbBid{
+					Bid: &openrtb2.Bid{
+						ImpID: "Imp2",
+						Price: 10,
+					},
+				},
+				nonBidReason: int(ErrorGeneral),
+				seat:         "seat1",
+			},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "Imp1",
+						StatusCode: int(ErrorGeneral),
+					},
+					{
+						ImpId:      "Imp2",
+						StatusCode: int(ErrorGeneral),
+						Ext: &openrtb_ext.NonBidExt{
+							Prebid: openrtb_ext.ExtResponseNonBidPrebid{
+								Bid: openrtb_ext.NonBidObject{
+									Price: 10,
+								},
+							},
+						},
+					},
+				},
+			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			snb := &nonBids{
-				seatNonBidsMap: tt.fields.seatNonBidsMap,
-			}
-			snb.addBid(tt.args.bid, tt.args.nonBidReason, tt.args.seat)
-			assert.Equalf(t, tt.want, snb.seatNonBidsMap, "expected seatNonBidsMap not nil")
+			snb := tt.fields.builder
+			snb.rejectBid(tt.args.bid, tt.args.nonBidReason, tt.args.seat)
+			assert.Equal(t, tt.want, snb)
 		})
 	}
 }
 
-func TestSeatNonBidsGet(t *testing.T) {
-	type fields struct {
-		snb *nonBids
-	}
+func TestAppend(t *testing.T) {
 	tests := []struct {
-		name   string
-		fields fields
-		want   []openrtb_ext.SeatNonBid
+		name     string
+		builder  SeatNonBidBuilder
+		toAppend []SeatNonBidBuilder
+		expected SeatNonBidBuilder
 	}{
 		{
-			name:   "get-seat-nonbids",
-			fields: fields{&nonBids{sampleSeatNonBidMap("bidder1", 2)}},
-			want:   sampleSeatBids("bidder1", 2),
+			name:     "nil_buider",
+			builder:  nil,
+			toAppend: []SeatNonBidBuilder{{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}},
+			expected: nil,
+		},
+		{
+			name:     "empty_builder",
+			builder:  SeatNonBidBuilder{},
+			toAppend: []SeatNonBidBuilder{{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}}},
+			expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+		},
+		{
+			name:     "append_one_different_seat",
+			builder:  SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+			toAppend: []SeatNonBidBuilder{{"seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}}},
+			expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}, "seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}},
+		},
+		{
+			name:     "append_multiple_different_seats",
+			builder:  SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+			toAppend: []SeatNonBidBuilder{{"seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}}, {"seat3": []openrtb_ext.NonBid{{ImpId: "imp3"}}}},
+			expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}, "seat2": []openrtb_ext.NonBid{{ImpId: "imp2"}}, "seat3": []openrtb_ext.NonBid{{ImpId: "imp3"}}},
+		},
+		{
+			name:     "nil_append",
+			builder:  SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+			toAppend: nil,
+			expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+		},
+		{
+			name:     "empty_append",
+			builder:  SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
+			toAppend: []SeatNonBidBuilder{},
+			expected: SeatNonBidBuilder{"seat1": []openrtb_ext.NonBid{{ImpId: "imp1"}}},
 		},
 		{
-			name:   "nil-seat-nonbids",
-			fields: fields{nil},
+			name: "append_multiple_same_seat",
+			builder: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{ImpId: "imp1"},
+				},
+			},
+			toAppend: []SeatNonBidBuilder{
+				{
+					"seat1": []openrtb_ext.NonBid{
+						{ImpId: "imp2"},
+					},
+				},
+			},
+			expected: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{ImpId: "imp1"},
+					{ImpId: "imp2"},
+				},
+			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := tt.fields.snb.get(); !assert.Equal(t, tt.want, got) {
-				t.Errorf("seatNonBids.get() = %v, want %v", got, tt.want)
-			}
+			tt.builder.append(tt.toAppend...)
+			assert.Equal(t, tt.expected, tt.builder)
 		})
 	}
 }
 
-var sampleSeatNonBidMap = func(seat string, nonBidCount int) map[string][]openrtb_ext.NonBid {
-	nonBids := make([]openrtb_ext.NonBid, 0)
-	for i := 0; i < nonBidCount; i++ {
-		nonBids = append(nonBids, openrtb_ext.NonBid{
-			Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}},
-		})
+func TestRejectImps(t *testing.T) {
+	tests := []struct {
+		name    string
+		impIDs  []string
+		builder SeatNonBidBuilder
+		want    SeatNonBidBuilder
+	}{
+		{
+			name:    "nil_imps",
+			impIDs:  nil,
+			builder: SeatNonBidBuilder{},
+			want:    SeatNonBidBuilder{},
+		},
+		{
+			name:    "empty_imps",
+			impIDs:  []string{},
+			builder: SeatNonBidBuilder{},
+			want:    SeatNonBidBuilder{},
+		},
+		{
+			name:    "one_imp",
+			impIDs:  []string{"imp1"},
+			builder: SeatNonBidBuilder{},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp1",
+						StatusCode: 300,
+					},
+				},
+			},
+		},
+		{
+			name:    "many_imps",
+			impIDs:  []string{"imp1", "imp2"},
+			builder: SeatNonBidBuilder{},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp1",
+						StatusCode: 300,
+					},
+					{
+						ImpId:      "imp2",
+						StatusCode: 300,
+					},
+				},
+			},
+		},
+		{
+			name:   "many_imps_appended_to_prepopulated_list",
+			impIDs: []string{"imp1", "imp2"},
+			builder: SeatNonBidBuilder{
+				"seat0": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp0",
+						StatusCode: 0,
+					},
+				},
+			},
+			want: SeatNonBidBuilder{
+				"seat0": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp0",
+						StatusCode: 0,
+					},
+				},
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp1",
+						StatusCode: 300,
+					},
+					{
+						ImpId:      "imp2",
+						StatusCode: 300,
+					},
+				},
+			},
+		},
+		{
+			name:   "many_imps_appended_to_prepopulated_list_same_seat",
+			impIDs: []string{"imp1", "imp2"},
+			builder: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp0",
+						StatusCode: 300,
+					},
+				},
+			},
+			want: SeatNonBidBuilder{
+				"seat1": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp0",
+						StatusCode: 300,
+					},
+					{
+						ImpId:      "imp1",
+						StatusCode: 300,
+					},
+					{
+						ImpId:      "imp2",
+						StatusCode: 300,
+					},
+				},
+			},
+		},
 	}
-	return map[string][]openrtb_ext.NonBid{
-		seat: nonBids,
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			test.builder.rejectImps(test.impIDs, 300, "seat1")
+
+			assert.Equal(t, len(test.builder), len(test.want))
+			for seat := range test.want {
+				assert.ElementsMatch(t, test.want[seat], test.builder[seat])
+			}
+		})
 	}
 }
 
-var sampleSeatBids = func(seat string, nonBidCount int) []openrtb_ext.SeatNonBid {
-	seatNonBids := make([]openrtb_ext.SeatNonBid, 0)
-	seatNonBid := openrtb_ext.SeatNonBid{
-		Seat:   seat,
-		NonBid: make([]openrtb_ext.NonBid, 0),
+func TestSlice(t *testing.T) {
+	tests := []struct {
+		name    string
+		builder SeatNonBidBuilder
+		want    []openrtb_ext.SeatNonBid
+	}{
+		{
+			name:    "nil",
+			builder: nil,
+			want:    []openrtb_ext.SeatNonBid{},
+		},
+		{
+			name:    "empty",
+			builder: SeatNonBidBuilder{},
+			want:    []openrtb_ext.SeatNonBid{},
+		},
+		{
+			name: "one_no_nonbids",
+			builder: SeatNonBidBuilder{
+				"a": []openrtb_ext.NonBid{},
+			},
+			want: []openrtb_ext.SeatNonBid{
+				{
+					NonBid: []openrtb_ext.NonBid{},
+					Seat:   "a",
+				},
+			},
+		},
+		{
+			name: "one_with_nonbids",
+			builder: SeatNonBidBuilder{
+				"a": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp1",
+						StatusCode: 100,
+					},
+					{
+						ImpId:      "imp2",
+						StatusCode: 200,
+					},
+				},
+			},
+			want: []openrtb_ext.SeatNonBid{
+				{
+					NonBid: []openrtb_ext.NonBid{
+						{
+							ImpId:      "imp1",
+							StatusCode: 100,
+						},
+						{
+							ImpId:      "imp2",
+							StatusCode: 200,
+						},
+					},
+					Seat: "a",
+				},
+			},
+		},
+		{
+			name: "many_no_nonbids",
+			builder: SeatNonBidBuilder{
+				"a": []openrtb_ext.NonBid{},
+				"b": []openrtb_ext.NonBid{},
+				"c": []openrtb_ext.NonBid{},
+			},
+			want: []openrtb_ext.SeatNonBid{
+				{
+					NonBid: []openrtb_ext.NonBid{},
+					Seat:   "a",
+				},
+				{
+					NonBid: []openrtb_ext.NonBid{},
+					Seat:   "b",
+				},
+				{
+					NonBid: []openrtb_ext.NonBid{},
+					Seat:   "c",
+				},
+			},
+		},
+		{
+			name: "many_with_nonbids",
+			builder: SeatNonBidBuilder{
+				"a": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp1",
+						StatusCode: 100,
+					},
+					{
+						ImpId:      "imp2",
+						StatusCode: 200,
+					},
+				},
+				"b": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp3",
+						StatusCode: 300,
+					},
+				},
+				"c": []openrtb_ext.NonBid{
+					{
+						ImpId:      "imp4",
+						StatusCode: 400,
+					},
+					{
+						ImpId:      "imp5",
+						StatusCode: 500,
+					},
+				},
+			},
+			want: []openrtb_ext.SeatNonBid{
+				{
+					NonBid: []openrtb_ext.NonBid{
+						{
+							ImpId:      "imp1",
+							StatusCode: 100,
+						},
+						{
+							ImpId:      "imp2",
+							StatusCode: 200,
+						},
+					},
+					Seat: "a",
+				},
+				{
+					NonBid: []openrtb_ext.NonBid{
+						{
+							ImpId:      "imp3",
+							StatusCode: 300,
+						},
+					},
+					Seat: "b",
+				},
+				{
+					NonBid: []openrtb_ext.NonBid{
+						{
+							ImpId:      "imp4",
+							StatusCode: 400,
+						},
+						{
+							ImpId:      "imp5",
+							StatusCode: 500,
+						},
+					},
+					Seat: "c",
+				},
+			},
+		},
 	}
-	for i := 0; i < nonBidCount; i++ {
-		seatNonBid.NonBid = append(seatNonBid.NonBid, openrtb_ext.NonBid{
-			Ext: openrtb_ext.NonBidExt{Prebid: openrtb_ext.ExtResponseNonBidPrebid{Bid: openrtb_ext.NonBidObject{}}},
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result := test.builder.Slice()
+			assert.ElementsMatch(t, test.want, result)
 		})
 	}
-	seatNonBids = append(seatNonBids, seatNonBid)
-	return seatNonBids
 }
diff --git a/exchange/utils.go b/exchange/utils.go
index 676c015ae0e..5e3feab15f5 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -1,7 +1,6 @@
 package exchange
 
 import (
-	"bytes"
 	"context"
 	"encoding/json"
 	"errors"
@@ -9,9 +8,6 @@ import (
 	"math/rand"
 	"strings"
 
-	"github.com/prebid/prebid-server/v2/ortb"
-
-	"github.com/buger/jsonparser"
 	"github.com/prebid/go-gdpr/vendorconsent"
 	gpplib "github.com/prebid/go-gpp"
 	gppConstants "github.com/prebid/go-gpp/constants"
@@ -23,6 +19,7 @@ import (
 	"github.com/prebid/prebid-server/v2/gdpr"
 	"github.com/prebid/prebid-server/v2/metrics"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/ortb"
 	"github.com/prebid/prebid-server/v2/privacy"
 	"github.com/prebid/prebid-server/v2/privacy/ccpa"
 	"github.com/prebid/prebid-server/v2/privacy/lmt"
@@ -32,6 +29,8 @@ import (
 	"github.com/prebid/prebid-server/v2/util/ptrutil"
 )
 
+var errInvalidRequestExt = errors.New("request.ext is invalid")
+
 var channelTypeMap = map[metrics.RequestType]config.ChannelType{
 	metrics.ReqTypeAMP:       config.ChannelAMP,
 	metrics.ReqTypeORTB2App:  config.ChannelApp,
@@ -49,6 +48,7 @@ type requestSplitter struct {
 	gdprPermsBuilder  gdpr.PermissionsBuilder
 	hostSChainNode    *openrtb2.SupplyChainNode
 	bidderInfo        config.BidderInfos
+	requestValidator  ortb.RequestValidator
 }
 
 // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
@@ -59,35 +59,51 @@ type requestSplitter struct {
 func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 	auctionReq AuctionRequest,
 	requestExt *openrtb_ext.ExtRequest,
-	gdprDefaultValue gdpr.Signal, bidAdjustmentFactors map[string]float64,
-) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
+	gdprSignal gdpr.Signal,
+	gdprEnforced bool,
+	bidAdjustmentFactors map[string]float64,
+) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
 	req := auctionReq.BidRequestWrapper
-	aliases, errs := parseAliases(req.BidRequest)
-	if len(errs) > 0 {
+	if err := PreloadExts(req); err != nil {
 		return
 	}
 
-	allowedBidderRequests = make([]BidderRequest, 0)
+	requestAliases, requestAliasesGVLIDs, errs := getRequestAliases(req)
+	if len(errs) > 0 {
+		return
+	}
 
 	bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses)
+	hasStoredAuctionResponses := len(auctionReq.StoredAuctionResponses) > 0
 
-	impsByBidder, err := splitImps(req.BidRequest.Imp)
+	impsByBidder, err := splitImps(req.BidRequest.Imp, rs.requestValidator, requestAliases, hasStoredAuctionResponses, auctionReq.StoredBidResponses)
 	if err != nil {
 		errs = []error{err}
 		return
 	}
 
-	aliasesGVLIDs, errs := parseAliasesGVLIDs(req.BidRequest)
-	if len(errs) > 0 {
+	explicitBuyerUIDs, err := extractBuyerUIDs(req.BidRequest.User)
+	if err != nil {
+		errs = []error{err}
 		return
 	}
+	lowerCaseExplicitBuyerUIDs := make(map[string]string)
+	for bidder, uid := range explicitBuyerUIDs {
+		lowerKey := strings.ToLower(bidder)
+		lowerCaseExplicitBuyerUIDs[lowerKey] = uid
+	}
 
-	var allBidderRequests []BidderRequest
-	allBidderRequests, errs = getAuctionBidderRequests(auctionReq, requestExt, rs.bidderToSyncerKey, impsByBidder, aliases, rs.hostSChainNode)
+	bidderParamsInReqExt, err := ExtractReqExtBidderParamsMap(req.BidRequest)
+	if err != nil {
+		errs = []error{err}
+		return
+	}
 
-	bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, aliases, auctionReq.BidderImpReplaceImpID)
-	//this function should be executed after getAuctionBidderRequests
-	allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq)
+	sChainWriter, err := schain.NewSChainWriter(requestExt, rs.hostSChainNode)
+	if err != nil {
+		errs = []error{err}
+		return
+	}
 
 	var gpp gpplib.GppContainer
 	if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 {
@@ -98,23 +114,12 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 		}
 	}
 
-	if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() {
-		//Apply BidAdjustmentFactor to imp.BidFloor
-		applyBidAdjustmentToFloor(allBidderRequests, bidAdjustmentFactors)
-	}
-
-	gdprSignal, err := getGDPR(req)
-	if err != nil {
-		errs = append(errs, err)
-	}
-
 	consent, err := getConsent(req, gpp)
 	if err != nil {
 		errs = append(errs, err)
 	}
-	gdprApplies := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes)
 
-	ccpaEnforcer, err := extractCCPA(req.BidRequest, rs.privacyConfig, &auctionReq.Account, aliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp)
+	ccpaEnforcer, err := extractCCPA(req.BidRequest, rs.privacyConfig, &auctionReq.Account, requestAliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp)
 	if err != nil {
 		errs = append(errs, err)
 	}
@@ -130,13 +135,8 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 	privacyLabels.COPPAEnforced = coppa
 	privacyLabels.LMTEnforced = lmt
 
-	var gdprEnforced bool
 	var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{}
 
-	if gdprApplies {
-		gdprEnforced = auctionReq.TCF2Config.ChannelEnabled(channelTypeMap[auctionReq.LegacyLabels.RType])
-	}
-
 	if gdprEnforced {
 		privacyLabels.GDPREnforced = true
 		parsedConsent, err := vendorconsent.ParseString(consent)
@@ -146,7 +146,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 		}
 
 		gdprRequestInfo := gdpr.RequestInfo{
-			AliasGVLIDs: aliasesGVLIDs,
+			AliasGVLIDs: requestAliasesGVLIDs,
 			Consent:     consent,
 			GDPRSignal:  gdprSignal,
 			PublisherID: auctionReq.LegacyLabels.PubID,
@@ -154,92 +154,270 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
 		gdprPerms = rs.gdprPermsBuilder(auctionReq.TCF2Config, gdprRequestInfo)
 	}
 
-	// bidder level privacy policies
-	for _, bidderRequest := range allBidderRequests {
-		// fetchBids activity
-		scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()}
-		fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req))
-		if !fetchBidsActivityAllowed {
-			// skip the call to a bidder if fetchBids activity is not allowed
-			// do not add this bidder to allowedBidderRequests
+	bidderRequests = make([]BidderRequest, 0, len(impsByBidder))
+
+	for bidder, imps := range impsByBidder {
+		fpdUserEIDsPresent := fpdUserEIDExists(req, auctionReq.FirstPartyData, bidder)
+		reqWrapperCopy := req.CloneAndClearImpWrappers()
+		bidRequestCopy := *req.BidRequest
+		reqWrapperCopy.BidRequest = &bidRequestCopy
+		reqWrapperCopy.Imp = imps
+
+		coreBidder, isRequestAlias := resolveBidder(bidder, requestAliases)
+
+		// apply bidder-specific schains
+		sChainWriter.Write(reqWrapperCopy, bidder)
+
+		// eid scrubbing
+		if err := removeUnpermissionedEids(reqWrapperCopy, bidder); err != nil {
+			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
 			continue
 		}
 
-		var auctionPermissions gdpr.AuctionPermissions
-		var gdprErr error
+		// generate bidder-specific request ext
+		err = buildRequestExtForBidder(bidder, reqWrapperCopy, bidderParamsInReqExt, auctionReq.Account.AlternateBidderCodes)
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
 
-		if gdprEnforced {
-			auctionPermissions, gdprErr = gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName)
-			if !auctionPermissions.AllowBidRequest {
-				// auction request is not permitted by GDPR
-				// do not add this bidder to allowedBidderRequests
-				rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName)
-				continue
-			}
+		// apply bid adjustments
+		if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() {
+			applyBidAdjustmentToFloor(reqWrapperCopy, bidder, bidAdjustmentFactors)
 		}
 
-		ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config}
+		// prepare user
+		syncerKey := rs.bidderToSyncerKey[string(coreBidder)]
+		hadSync := prepareUser(reqWrapperCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionReq.UserSyncs)
 
-		// FPD should be applied before policies, otherwise it overrides policies and activities restricted data
-		applyFPD(auctionReq.FirstPartyData, bidderRequest)
+		auctionPermissions := gdprPerms.AuctionActivitiesAllowed(ctx, coreBidder, openrtb_ext.BidderName(bidder))
 
-		reqWrapper := &openrtb_ext.RequestWrapper{
-			BidRequest: ortb.CloneBidRequestPartial(bidderRequest.BidRequest),
+		// privacy blocking
+		if rs.isBidderBlockedByPrivacy(reqWrapperCopy, auctionReq.Activities, auctionPermissions, coreBidder, openrtb_ext.BidderName(bidder)) {
+			continue
 		}
 
-		passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req))
-		if !passIDActivityAllowed {
-			//UFPD
-			privacy.ScrubUserFPD(reqWrapper)
-		} else {
-			// run existing policies (GDPR, CCPA, COPPA, LMT)
-			// potentially block passing IDs based on GDPR
-			if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) {
-				privacy.ScrubGdprID(reqWrapper)
-			}
-			// potentially block passing IDs based on CCPA
-			if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
-				privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
+		// fpd
+		applyFPD(auctionReq.FirstPartyData, coreBidder, openrtb_ext.BidderName(bidder), isRequestAlias, reqWrapperCopy, fpdUserEIDsPresent)
+
+		// privacy scrubbing
+		if err := rs.applyPrivacy(reqWrapperCopy, coreBidder, bidder, auctionReq, auctionPermissions, ccpaEnforcer, lmt, coppa); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		// GPP downgrade: always downgrade unless we can confirm GPP is supported
+		if shouldSetLegacyPrivacy(rs.bidderInfo, string(coreBidder)) {
+			setLegacyGDPRFromGPP(reqWrapperCopy, gpp)
+			setLegacyUSPFromGPP(reqWrapperCopy, gpp)
+		}
+
+		// remove imps with stored responses so they aren't sent to the bidder
+		if impResponses, ok := bidderImpWithBidResp[openrtb_ext.BidderName(bidder)]; ok {
+			removeImpsWithStoredResponses(reqWrapperCopy, impResponses)
+		}
+
+		// down convert
+		info, ok := rs.bidderInfo[bidder]
+		if !ok || info.OpenRTB == nil || info.OpenRTB.Version != "2.6" {
+			reqWrapperCopy.Regs = ortb.CloneRegs(reqWrapperCopy.Regs)
+			if err := openrtb_ext.ConvertDownTo25(reqWrapperCopy); err != nil {
+				errs = append(errs, err)
+				continue
 			}
 		}
 
-		passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req))
-		if !passGeoActivityAllowed {
-			privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
+		// sync wrapper
+		if err := reqWrapperCopy.RebuildRequest(); err != nil {
+			errs = append(errs, err)
+			continue
+		}
+
+		// choose labels
+		bidderLabels := metrics.AdapterLabels{
+			Adapter: coreBidder,
+		}
+		if !hadSync && req.BidRequest.App == nil {
+			bidderLabels.CookieFlag = metrics.CookieFlagNo
 		} else {
-			// run existing policies (GDPR, CCPA, COPPA, LMT)
-			// potentially block passing geo based on GDPR
-			if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassGeo) {
-				privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
-			}
-			// potentially block passing geo based on CCPA
-			if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
-				privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
-			}
+			bidderLabels.CookieFlag = metrics.CookieFlagYes
+		}
+		if len(reqWrapperCopy.Imp) > 0 {
+			bidderLabels.Source = auctionReq.LegacyLabels.Source
+			bidderLabels.RType = auctionReq.LegacyLabels.RType
+			bidderLabels.PubID = auctionReq.LegacyLabels.PubID
+			bidderLabels.CookieFlag = auctionReq.LegacyLabels.CookieFlag
+			bidderLabels.AdapterBids = metrics.AdapterBidPresent
 		}
 
-		if lmt || coppa {
-			privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa)
+		bidderRequest := BidderRequest{
+			BidderName:            openrtb_ext.BidderName(bidder),
+			BidderCoreName:        coreBidder,
+			BidRequest:            reqWrapperCopy.BidRequest,
+			IsRequestAlias:        isRequestAlias,
+			BidderStoredResponses: bidderImpWithBidResp[openrtb_ext.BidderName(bidder)],
+			ImpReplaceImpId:       auctionReq.BidderImpReplaceImpID[bidder],
+			BidderLabels:          bidderLabels,
 		}
+		bidderRequests = append(bidderRequests, bidderRequest)
+	}
 
-		passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req))
-		if !passTIDAllowed {
-			privacy.ScrubTID(reqWrapper)
+	return
+}
+
+// fpdUserEIDExists determines if req fpd config had User.EIDs
+func fpdUserEIDExists(req *openrtb_ext.RequestWrapper, fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, bidder string) bool {
+	fpdToApply, exists := fpd[openrtb_ext.BidderName(bidder)]
+	if !exists || fpdToApply == nil {
+		return false
+	}
+	if fpdToApply.User == nil {
+		return false
+	}
+	fpdUserEIDs := fpdToApply.User.EIDs
+
+	if len(fpdUserEIDs) == 0 {
+		return false
+	}
+	if req.User == nil {
+		return true
+	}
+
+	reqUserEIDs := req.User.EIDs
+
+	if len(reqUserEIDs) != len(fpdUserEIDs) {
+		return true
+	}
+
+	// if bidder fpd didn't have user.eids then user.eids will remain the same
+	// hence we can use the same index to compare elements
+	for i := range reqUserEIDs {
+		pReqUserEID := &reqUserEIDs[i]
+		pFpdUserEID := &fpdUserEIDs[i]
+		if pReqUserEID != pFpdUserEID {
+			return true
 		}
+	}
+	return false
+}
 
-		reqWrapper.RebuildRequest()
-		bidderRequest.BidRequest = reqWrapper.BidRequest
+// removeImpsWithStoredResponses deletes imps with stored bid resp
+func removeImpsWithStoredResponses(req *openrtb_ext.RequestWrapper, impBidResponses map[string]json.RawMessage) {
+	if len(impBidResponses) == 0 {
+		return
+	}
 
-		allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
+	imps := req.Imp
+	req.Imp = nil //to indicate this bidder doesn't have real requests
+	for _, imp := range imps {
+		if _, ok := impBidResponses[imp.ID]; !ok {
+			//add real imp back to request
+			req.Imp = append(req.Imp, imp)
+		}
+	}
+}
 
-		// GPP downgrade: always downgrade unless we can confirm GPP is supported
-		if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) {
-			setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp)
-			setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp)
+// PreloadExts ensures all exts have been unmarshalled into wrapper ext objects
+func PreloadExts(req *openrtb_ext.RequestWrapper) error {
+	if req == nil {
+		return nil
+	}
+	if _, err := req.GetRequestExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetUserExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetDeviceExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetRegExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetSiteExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetDOOHExt(); err != nil {
+		return err
+	}
+	if _, err := req.GetSourceExt(); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (rs *requestSplitter) isBidderBlockedByPrivacy(r *openrtb_ext.RequestWrapper, activities privacy.ActivityControl, auctionPermissions gdpr.AuctionPermissions, coreBidder, bidderName openrtb_ext.BidderName) bool {
+	// activities control
+	scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName.String()}
+	fetchBidsActivityAllowed := activities.Allow(privacy.ActivityFetchBids, scope, privacy.NewRequestFromBidRequest(*r))
+	if !fetchBidsActivityAllowed {
+		return true
+	}
+
+	// gdpr
+	if !auctionPermissions.AllowBidRequest {
+		rs.me.RecordAdapterGDPRRequestBlocked(coreBidder)
+		return true
+	}
+
+	return false
+}
+
+func (rs *requestSplitter) applyPrivacy(reqWrapper *openrtb_ext.RequestWrapper, coreBidderName openrtb_ext.BidderName, bidderName string, auctionReq AuctionRequest, auctionPermissions gdpr.AuctionPermissions, ccpaEnforcer privacy.PolicyEnforcer, lmt bool, coppa bool) error {
+	scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName}
+	ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config}
+
+	bidRequest := ortb.CloneBidRequestPartial(reqWrapper.BidRequest)
+	reqWrapper.BidRequest = bidRequest
+
+	passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
+	buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != ""
+	buyerUIDRemoved := false
+	if !passIDActivityAllowed {
+		privacy.ScrubUserFPD(reqWrapper)
+		buyerUIDRemoved = true
+	} else {
+		if !auctionPermissions.PassID {
+			privacy.ScrubGdprID(reqWrapper)
+			buyerUIDRemoved = true
+		}
+
+		if ccpaEnforcer.ShouldEnforce(bidderName) {
+			privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
+			buyerUIDRemoved = true
 		}
 	}
+	if buyerUIDSet && buyerUIDRemoved {
+		rs.me.RecordAdapterBuyerUIDScrubbed(coreBidderName)
+	}
 
-	return
+	passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
+	if !passGeoActivityAllowed {
+		privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
+	} else {
+		if !auctionPermissions.PassGeo {
+			privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
+		}
+		if ccpaEnforcer.ShouldEnforce(bidderName) {
+			privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
+		}
+	}
+
+	if lmt || coppa {
+		privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa)
+	}
+
+	passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
+	if !passTIDAllowed {
+		privacy.ScrubTID(reqWrapper)
+	}
+
+	if err := reqWrapper.RebuildRequest(); err != nil {
+		return err
+	}
+
+	// *bidRequest = *reqWrapper.BidRequest
+	return nil
 }
 
 func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool {
@@ -259,14 +437,14 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT
 	return privacyConfig.CCPA.Enforce
 }
 
-func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) {
+func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, requestAliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) {
 	// Quick extra wrapper until RequestWrapper makes its way into CleanRequests
 	ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}, gpp)
 	if err != nil {
 		return privacy.NilPolicyEnforcer{}, err
 	}
 
-	validBidders := GetValidBidders(aliases)
+	validBidders := GetValidBidders(requestAliases)
 	ccpaParsedPolicy, err := ccpaPolicy.Parse(validBidders)
 	if err != nil {
 		return privacy.NilPolicyEnforcer{}, err
@@ -312,147 +490,47 @@ func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]j
 	return bidderParams, nil
 }
 
-func getAuctionBidderRequests(auctionRequest AuctionRequest,
-	requestExt *openrtb_ext.ExtRequest,
-	bidderToSyncerKey map[string]string,
-	impsByBidder map[string][]openrtb2.Imp,
-	aliases map[string]string,
-	hostSChainNode *openrtb2.SupplyChainNode) ([]BidderRequest, []error) {
-
-	bidderRequests := make([]BidderRequest, 0, len(impsByBidder))
-	req := auctionRequest.BidRequestWrapper
-	explicitBuyerUIDs, err := extractBuyerUIDs(req.BidRequest.User)
+func buildRequestExtForBidder(bidder string, req *openrtb_ext.RequestWrapper, reqExtBidderParams map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) error {
+	reqExt, err := req.GetRequestExt()
 	if err != nil {
-		return nil, []error{err}
-	}
-
-	bidderParamsInReqExt, err := ExtractReqExtBidderParamsMap(req.BidRequest)
-	if err != nil {
-		return nil, []error{err}
-	}
-
-	sChainWriter, err := schain.NewSChainWriter(requestExt, hostSChainNode)
-	if err != nil {
-		return nil, []error{err}
-	}
-
-	lowerCaseExplicitBuyerUIDs := make(map[string]string)
-	for bidder, uid := range explicitBuyerUIDs {
-		lowerKey := strings.ToLower(bidder)
-		lowerCaseExplicitBuyerUIDs[lowerKey] = uid
-	}
-
-	var errs []error
-	for bidder, imps := range impsByBidder {
-		coreBidder, isRequestAlias := resolveBidder(bidder, aliases)
-
-		reqCopy := *req.BidRequest
-		reqCopy.Imp = imps
-
-		sChainWriter.Write(&reqCopy, bidder)
-
-		reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionRequest.Account.AlternateBidderCodes)
-		if err != nil {
-			return nil, []error{err}
-		}
-
-		if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil {
-			errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err))
-			continue
-		}
-
-		bidderRequest := BidderRequest{
-			BidderName:     openrtb_ext.BidderName(bidder),
-			BidderCoreName: coreBidder,
-			IsRequestAlias: isRequestAlias,
-			BidRequest:     &reqCopy,
-			BidderLabels: metrics.AdapterLabels{
-				Source:      auctionRequest.LegacyLabels.Source,
-				RType:       auctionRequest.LegacyLabels.RType,
-				Adapter:     coreBidder,
-				PubID:       auctionRequest.LegacyLabels.PubID,
-				CookieFlag:  auctionRequest.LegacyLabels.CookieFlag,
-				AdapterBids: metrics.AdapterBidPresent,
-			},
-		}
-
-		syncerKey := bidderToSyncerKey[string(coreBidder)]
-		if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil {
-			bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo
-		} else {
-			bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes
-		}
-
-		bidderRequests = append(bidderRequests, bidderRequest)
+		return err
 	}
-	return bidderRequests, errs
-}
+	prebid := reqExt.GetPrebid()
 
-func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) {
-	// Resolve alternatebiddercode for current bidder
+	// Resolve alternatebiddercode
 	var reqABC *openrtb_ext.ExtAlternateBidderCodes
-	if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil {
-		reqABC = requestExtParsed.Prebid.AlternateBidderCodes
+	if prebid != nil && prebid.AlternateBidderCodes != nil {
+		reqABC = prebid.AlternateBidderCodes
 	}
 	alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC)
 
-	if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil {
-		return nil, nil
-	}
-
-	// Resolve Bidder Params
-	var bidderParams json.RawMessage
-	if bidderParamsInReqExt != nil {
-		bidderParams = bidderParamsInReqExt[bidder]
-	}
-
-	// Copy Allowed Fields
-	// Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary
-	prebid := openrtb_ext.ExtRequestPrebid{
-		BidderParams:         bidderParams,
-		AlternateBidderCodes: alternateBidderCodes,
-	}
-
-	if requestExtParsed != nil {
-		prebid.Channel = requestExtParsed.Prebid.Channel
-		prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions
-		prebid.Debug = requestExtParsed.Prebid.Debug
-		prebid.Integration = requestExtParsed.Prebid.Integration
-		prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes)
-		prebid.Sdk = requestExtParsed.Prebid.Sdk
-		prebid.Server = requestExtParsed.Prebid.Server
-	}
-
-	// Marshal New Prebid Object
-	prebidJson, err := jsonutil.Marshal(prebid)
-	if err != nil {
-		return nil, err
-	}
-
-	// Parse Existing Ext
-	extMap := make(map[string]json.RawMessage)
-	if len(requestExt) != 0 {
-		if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil {
-			return nil, err
+	var prebidNew openrtb_ext.ExtRequestPrebid
+	if prebid == nil {
+		prebidNew = openrtb_ext.ExtRequestPrebid{
+			BidderParams:         reqExtBidderParams[bidder],
+			AlternateBidderCodes: alternateBidderCodes,
 		}
-	}
-
-	// Update Ext With Prebid Json
-	if bytes.Equal(prebidJson, []byte(`{}`)) {
-		delete(extMap, "prebid")
 	} else {
-		extMap["prebid"] = prebidJson
+		// Copy Allowed Fields
+		// Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary
+		prebidNew = openrtb_ext.ExtRequestPrebid{
+			BidderParams:         reqExtBidderParams[bidder],
+			AlternateBidderCodes: alternateBidderCodes,
+			Channel:              prebid.Channel,
+			CurrencyConversions:  prebid.CurrencyConversions,
+			Debug:                prebid.Debug,
+			Integration:          prebid.Integration,
+			MultiBid:             buildRequestExtMultiBid(bidder, prebid.MultiBid, alternateBidderCodes),
+			Sdk:                  prebid.Sdk,
+			Server:               prebid.Server,
+		}
 	}
 
-	if len(extMap) > 0 {
-		return jsonutil.Marshal(extMap)
-	} else {
-		return nil, nil
-	}
+	reqExt.SetPrebid(&prebidNew)
+	return nil
 }
 
 func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes {
-
 	if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil {
 		return altBidderCodes
 	}
@@ -564,7 +642,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) {
 // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key.
 //
 // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them.
-func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) {
+func splitImps(imps []openrtb2.Imp, requestValidator ortb.RequestValidator, requestAliases map[string]string, hasStoredAuctionResponses bool, storedBidResponses stored_responses.ImpBidderStoredResp) (map[string][]openrtb2.Imp, error) {
 	bidderImps := make(map[string][]openrtb2.Imp)
 
 	for i, imp := range imps {
@@ -585,6 +663,11 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) {
 			jsonutil.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder)
 		}
 
+		var impExtPrebidImp map[string]json.RawMessage
+		if impExtPrebidImpJSON, exists := impExtPrebid["imp"]; exists {
+			jsonutil.Unmarshal(impExtPrebidImpJSON, &impExtPrebidImp)
+		}
+
 		sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid)
 		if err != nil {
 			return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err)
@@ -593,6 +676,22 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) {
 		for bidder, bidderExt := range impExtPrebidBidder {
 			impCopy := imp
 
+			if impBidderFPD, exists := impExtPrebidImp[bidder]; exists {
+				if err := mergeImpFPD(&impCopy, impBidderFPD, i); err != nil {
+					return nil, err
+				}
+				impWrapper := openrtb_ext.ImpWrapper{Imp: &impCopy}
+				cfg := ortb.ValidationConfig{
+					SkipBidderParams: true,
+					SkipNative:       true,
+				}
+				if err := requestValidator.ValidateImp(&impWrapper, cfg, i, requestAliases, hasStoredAuctionResponses, storedBidResponses); err != nil {
+					return nil, &errortypes.InvalidImpFirstPartyData{
+						Message: fmt.Sprintf("merging bidder imp first party data for imp %s results in an invalid imp: %v", imp.ID, err),
+					}
+				}
+			}
+
 			sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt
 
 			impExtJSON, err := jsonutil.Marshal(sanitizedImpExt)
@@ -608,6 +707,16 @@ func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) {
 	return bidderImps, nil
 }
 
+func mergeImpFPD(imp *openrtb2.Imp, fpd json.RawMessage, index int) error {
+	if err := jsonutil.MergeClone(imp, fpd); err != nil {
+		if strings.Contains(err.Error(), "invalid json on existing object") {
+			return fmt.Errorf("invalid imp ext for imp[%d]", index)
+		}
+		return fmt.Errorf("invalid first party data for imp[%d]", index)
+	}
+	return nil
+}
+
 var allowedImpExtFields = map[string]interface{}{
 	openrtb_ext.AuctionEnvironmentKey:       struct{}{},
 	openrtb_ext.FirstPartyDataExtKey:        struct{}{},
@@ -657,7 +766,7 @@ func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map
 //
 // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias.
 // It returns true if a Cookie User Sync existed, and false otherwise.
-func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool {
+func prepareUser(req *openrtb_ext.RequestWrapper, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool {
 	cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey)
 
 	if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok {
@@ -685,42 +794,32 @@ func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User {
 	return user
 }
 
-// removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder
-func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error {
+// removeUnpermissionedEids modifies the request to remove any request.user.eids not permissions for the specific bidder
+func removeUnpermissionedEids(reqWrapper *openrtb_ext.RequestWrapper, bidder string) error {
 	// ensure request might have eids (as much as we can check before unmarshalling)
-	if request.User == nil || len(request.User.Ext) == 0 {
+	if reqWrapper.User == nil || len(reqWrapper.User.EIDs) == 0 {
 		return nil
 	}
 
 	// ensure request has eid permissions to enforce
-	if requestExt == nil || requestExt.Prebid.Data == nil || len(requestExt.Prebid.Data.EidPermissions) == 0 {
-		return nil
-	}
-
-	// low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive.
-	var userExt map[string]json.RawMessage
-	if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil {
+	reqExt, err := reqWrapper.GetRequestExt()
+	if err != nil {
 		return err
 	}
-
-	eidsJSON, eidsSpecified := userExt["eids"]
-	if !eidsSpecified {
+	if reqExt == nil {
 		return nil
 	}
 
-	var eids []openrtb2.EID
-	if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil {
-		return err
-	}
-
-	// exit early if there are no eids (empty array)
-	if len(eids) == 0 {
+	reqExtPrebid := reqExt.GetPrebid()
+	if reqExtPrebid == nil || reqExtPrebid.Data == nil || len(reqExtPrebid.Data.EidPermissions) == 0 {
 		return nil
 	}
 
+	eids := reqWrapper.User.EIDs
+
 	// translate eid permissions to a map for quick lookup
 	eidRules := make(map[string][]string)
-	for _, p := range requestExt.Prebid.Data.EidPermissions {
+	for _, p := range reqExtPrebid.Data.EidPermissions {
 		eidRules[p.Source] = p.Bidders
 	}
 
@@ -748,37 +847,14 @@ func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, reque
 		return nil
 	}
 
-	// marshal eidsAllowed back to userExt
 	if len(eidsAllowed) == 0 {
-		delete(userExt, "eids")
+		reqWrapper.User.EIDs = nil
 	} else {
-		eidsRaw, err := jsonutil.Marshal(eidsAllowed)
-		if err != nil {
-			return err
-		}
-		userExt["eids"] = eidsRaw
-	}
-
-	// exit early if userExt is empty
-	if len(userExt) == 0 {
-		setUserExtWithCopy(request, nil)
-		return nil
+		reqWrapper.User.EIDs = eidsAllowed
 	}
-
-	userExtJSON, err := jsonutil.Marshal(userExt)
-	if err != nil {
-		return err
-	}
-	setUserExtWithCopy(request, userExtJSON)
 	return nil
 }
 
-func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) {
-	userCopy := *request.User
-	userCopy.Ext = userExtJSON
-	request.User = &userCopy
-}
-
 // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned.
 func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) {
 	normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder)
@@ -790,36 +866,23 @@ func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext
 	return normalisedBidderName, false
 }
 
-// parseAliases parses the aliases from the BidRequest
-func parseAliases(orig *openrtb2.BidRequest) (map[string]string, []error) {
-	var aliases map[string]string
-	if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliases"); dataType == jsonparser.Object && err == nil {
-		if err := jsonutil.Unmarshal(value, &aliases); err != nil {
-			return nil, []error{err}
-		}
-	} else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError {
-		return nil, []error{err}
+func getRequestAliases(req *openrtb_ext.RequestWrapper) (map[string]string, map[string]uint16, []error) {
+	reqExt, err := req.GetRequestExt()
+	if err != nil {
+		return nil, nil, []error{errInvalidRequestExt}
 	}
-	return aliases, nil
-}
 
-// parseAliasesGVLIDs parses the Bidder Alias GVLIDs from the BidRequest
-func parseAliasesGVLIDs(orig *openrtb2.BidRequest) (map[string]uint16, []error) {
-	var aliasesGVLIDs map[string]uint16
-	if value, dataType, _, err := jsonparser.Get(orig.Ext, openrtb_ext.PrebidExtKey, "aliasgvlids"); dataType == jsonparser.Object && err == nil {
-		if err := jsonutil.Unmarshal(value, &aliasesGVLIDs); err != nil {
-			return nil, []error{err}
-		}
-	} else if dataType != jsonparser.NotExist && err != jsonparser.KeyPathNotFoundError {
-		return nil, []error{err}
+	if prebid := reqExt.GetPrebid(); prebid != nil {
+		return prebid.Aliases, prebid.AliasGVLIDs, nil
 	}
-	return aliasesGVLIDs, nil
+
+	return nil, nil, nil
 }
 
-func GetValidBidders(aliases map[string]string) map[string]struct{} {
+func GetValidBidders(requestAliases map[string]string) map[string]struct{} {
 	validBidders := openrtb_ext.BuildBidderNameHashSet()
 
-	for k := range aliases {
+	for k := range requestAliases {
 		validBidders[k] = struct{}{}
 	}
 
@@ -924,14 +987,19 @@ func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid)
 	return nil
 }
 
-func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) {
+func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData,
+	coreBidderName openrtb_ext.BidderName,
+	bidderName openrtb_ext.BidderName,
+	isRequestAlias bool,
+	reqWrapper *openrtb_ext.RequestWrapper,
+	fpdUserEIDsPresent bool) {
 	if fpd == nil {
 		return
 	}
 
-	bidder := r.BidderCoreName
-	if r.IsRequestAlias {
-		bidder = r.BidderName
+	bidder := coreBidderName
+	if isRequestAlias {
+		bidder = bidderName
 	}
 
 	fpdToApply, exists := fpd[bidder]
@@ -940,77 +1008,31 @@ func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyD
 	}
 
 	if fpdToApply.Site != nil {
-		r.BidRequest.Site = fpdToApply.Site
+		reqWrapper.Site = fpdToApply.Site
 	}
 
 	if fpdToApply.App != nil {
-		r.BidRequest.App = fpdToApply.App
+		reqWrapper.App = fpdToApply.App
 	}
 
 	if fpdToApply.User != nil {
-		//BuyerUID is a value obtained between fpd extraction and fpd application.
-		//BuyerUID needs to be set back to fpd before applying this fpd to final bidder request
-		if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 {
-			fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID
-		}
-		r.BidRequest.User = fpdToApply.User
-	}
-}
-
-func buildBidResponseRequest(req *openrtb2.BidRequest,
-	bidderImpResponses stored_responses.BidderImpsWithBidResponses,
-	aliases map[string]string,
-	bidderImpReplaceImpID stored_responses.BidderImpReplaceImpID) map[openrtb_ext.BidderName]BidderRequest {
-
-	bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest)
-
-	for bidderName, impResps := range bidderImpResponses {
-		resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), aliases)
-		bidderToBidderResponse[bidderName] = BidderRequest{
-			BidRequest:            req,
-			BidderCoreName:        resolvedBidder,
-			BidderName:            bidderName,
-			BidderStoredResponses: impResps,
-			ImpReplaceImpId:       bidderImpReplaceImpID[string(bidderName)],
-			IsRequestAlias:        isRequestAlias,
-			BidderLabels:          metrics.AdapterLabels{Adapter: resolvedBidder},
-		}
-	}
-	return bidderToBidderResponse
-}
-
-func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderReq map[openrtb_ext.BidderName]BidderRequest) []BidderRequest {
-	if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) == 0 {
-		return allBidderRequests
-	}
-	if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) > 0 {
-		for _, v := range bidderNameToBidderReq {
-			allBidderRequests = append(allBidderRequests, v)
-		}
-		return allBidderRequests
-	} else if len(allBidderRequests) > 0 && len(bidderNameToBidderReq) > 0 {
-		//merge bidder requests with real imps and imps with stored resp
-		for bn, br := range bidderNameToBidderReq {
-			found := false
-			for i, ar := range allBidderRequests {
-				if ar.BidderName == bn {
-					//bidder req with real imps and imps with stored resp
-					allBidderRequests[i].BidderStoredResponses = br.BidderStoredResponses
-					found = true
-					break
-				}
+		if reqWrapper.User != nil {
+			if len(reqWrapper.User.BuyerUID) > 0 {
+				//BuyerUID is a value obtained between fpd extraction and fpd application.
+				//BuyerUID needs to be set back to fpd before applying this fpd to final bidder request
+				fpdToApply.User.BuyerUID = reqWrapper.User.BuyerUID
 			}
-			if !found {
-				//bidder req with stored bid responses only
-				br.BidRequest.Imp = nil // to indicate this bidder request has bidder responses only
-				allBidderRequests = append(allBidderRequests, br)
+
+			// if FPD config didn't have user.eids - use reqWrapper.User.EIDs after removeUnpermissionedEids
+			if !fpdUserEIDsPresent {
+				fpdToApply.User.EIDs = reqWrapper.User.EIDs
 			}
 		}
+		reqWrapper.User = fpdToApply.User
 	}
-	return allBidderRequests
 }
 
-func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) {
+func setLegacyGDPRFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) {
 	if r.Regs != nil && r.Regs.GDPR == nil {
 		if r.Regs.GPPSID != nil {
 			// Set to 0 unless SID exists
@@ -1039,13 +1061,12 @@ func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) {
 			}
 		}
 	}
-
 }
-func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) {
+
+func setLegacyUSPFromGPP(r *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) {
 	if r.Regs == nil {
 		return
 	}
-
 	if len(r.Regs.USPrivacy) > 0 || r.Regs.GPPSID == nil {
 		return
 	}
@@ -1060,7 +1081,6 @@ func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) {
 			}
 		}
 	}
-
 }
 
 func WrapJSONInData(data []byte) []byte {
@@ -1121,24 +1141,20 @@ func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
 	}
 }
 
-func applyBidAdjustmentToFloor(allBidderRequests []BidderRequest, bidAdjustmentFactors map[string]float64) {
-
-	if len(bidAdjustmentFactors) == 0 {
+func applyBidAdjustmentToFloor(req *openrtb_ext.RequestWrapper, bidder string, adjustmentFactors map[string]float64) {
+	if len(adjustmentFactors) == 0 {
 		return
 	}
 
-	for _, bidderRequest := range allBidderRequests {
-		bidAdjustment := 1.0
-
-		if bidAdjustemntValue, ok := bidAdjustmentFactors[string(bidderRequest.BidderName)]; ok {
-			bidAdjustment = bidAdjustemntValue
-		}
+	bidAdjustment := 1.0
+	if v, ok := adjustmentFactors[bidder]; ok && v != 0.0 {
+		bidAdjustment = v
+	}
 
-		if bidAdjustment != 1.0 {
-			for index, imp := range bidderRequest.BidRequest.Imp {
-				imp.BidFloor = imp.BidFloor / bidAdjustment
-				bidderRequest.BidRequest.Imp[index] = imp
-			}
+	if bidAdjustment != 1.0 {
+		for index, imp := range req.Imp {
+			imp.BidFloor = imp.BidFloor / bidAdjustment
+			req.Imp[index] = imp
 		}
 	}
 }
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 0c8153b6b11..0d6f094aece 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -4,12 +4,9 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
-	"fmt"
 	"sort"
 	"testing"
 
-	"github.com/prebid/prebid-server/v2/stored_responses"
-
 	gpplib "github.com/prebid/go-gpp"
 	"github.com/prebid/go-gpp/constants"
 	"github.com/prebid/openrtb/v20/openrtb2"
@@ -24,6 +21,7 @@ import (
 	"github.com/prebid/prebid-server/v2/util/ptrutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
 )
 
 const deviceUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"
@@ -45,7 +43,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (gdpr.AuctionPermissions, error) {
+func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
 	permissions := gdpr.AuctionPermissions{
 		PassGeo: p.passGeo,
 		PassID:  p.passID,
@@ -53,7 +51,7 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo
 
 	if p.allowAllBidders {
 		permissions.AllowBidRequest = true
-		return permissions, p.activitiesError
+		return permissions
 	}
 
 	for _, allowedBidder := range p.allowedBidders {
@@ -62,7 +60,7 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo
 		}
 	}
 
-	return permissions, p.activitiesError
+	return permissions
 }
 
 type fakePermissionsBuilder struct {
@@ -92,10 +90,11 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest,
 
 func TestSplitImps(t *testing.T) {
 	testCases := []struct {
-		description   string
-		givenImps     []openrtb2.Imp
-		expectedImps  map[string][]openrtb2.Imp
-		expectedError string
+		description     string
+		givenImps       []openrtb2.Imp
+		validatorErrors []error
+		expectedImps    map[string][]openrtb2.Imp
+		expectedError   string
 	}{
 		{
 			description:   "Nil",
@@ -208,10 +207,105 @@ func TestSplitImps(t *testing.T) {
 			},
 			expectedError: "invalid json for imp[0]: do not know how to skip: 109",
 		},
+		{
+			description: "Malformed imp.ext.prebid.imp",
+			givenImps: []openrtb2.Imp{
+				{ID: "imp1", Ext: json.RawMessage(`{"prebid": {"imp": malformed}}`)},
+			},
+			expectedError: "invalid json for imp[0]: do not know how to skip: 109",
+		},
+		{
+			description: "valid FPD at imp.ext.prebid.imp for valid bidder",
+			givenImps: []openrtb2.Imp{
+				{
+					ID: "imp1",
+					Banner: &openrtb2.Banner{
+						Format: []openrtb2.Format{
+							{
+								W: 10,
+								H: 20,
+							},
+						},
+					},
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":30,"h":40}]}}}}}`),
+				},
+			},
+			expectedImps: map[string][]openrtb2.Imp{
+				"bidderA": {
+					{
+						ID: "impFPD",
+						Banner: &openrtb2.Banner{
+							Format: []openrtb2.Format{
+								{
+									W: 30,
+									H: 40,
+								},
+							},
+						},
+						Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`),
+					},
+				},
+			},
+			expectedError: "",
+		},
+		{
+			description: "valid FPD at imp.ext.prebid.imp for unknown bidder",
+			givenImps: []openrtb2.Imp{
+				{
+					ID: "imp1",
+					Banner: &openrtb2.Banner{
+						Format: []openrtb2.Format{
+							{
+								W: 10,
+								H: 20,
+							},
+						},
+					},
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderB":{"imp1paramB":"imp1valueB"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":30,"h":40}]}}}}}`),
+				},
+			},
+			expectedImps: map[string][]openrtb2.Imp{
+				"bidderB": {
+					{
+						ID: "imp1",
+						Banner: &openrtb2.Banner{
+							Format: []openrtb2.Format{
+								{
+									W: 10,
+									H: 20,
+								},
+							},
+						},
+						Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`),
+					},
+				},
+			},
+			expectedError: "",
+		},
+		{
+			description: "invalid FPD at imp.ext.prebid.imp for valid bidder",
+			givenImps: []openrtb2.Imp{
+				{
+					ID: "imp1",
+					Banner: &openrtb2.Banner{
+						Format: []openrtb2.Format{
+							{
+								W: 10,
+								H: 20,
+							},
+						},
+					},
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"}},"imp":{"bidderA":{"id":"impFPD", "banner":{"format":[{"w":0,"h":0}]}}}}}`),
+				},
+			},
+			validatorErrors: []error{errors.New("some error")},
+			expectedImps:    nil,
+			expectedError:   "merging bidder imp first party data for imp imp1 results in an invalid imp: [some error]",
+		},
 	}
 
 	for _, test := range testCases {
-		imps, err := splitImps(test.givenImps)
+		imps, err := splitImps(test.givenImps, &mockRequestValidator{errors: test.validatorErrors}, nil, false, nil)
 
 		if test.expectedError == "" {
 			assert.NoError(t, err, test.description+":err")
@@ -223,6 +317,201 @@ func TestSplitImps(t *testing.T) {
 	}
 }
 
+func TestMergeImpFPD(t *testing.T) {
+	imp1 := &openrtb2.Imp{
+		ID: "imp1",
+		Banner: &openrtb2.Banner{
+			W: ptrutil.ToPtr[int64](200),
+			H: ptrutil.ToPtr[int64](400),
+		},
+	}
+
+	tests := []struct {
+		description string
+		imp         *openrtb2.Imp
+		fpd         json.RawMessage
+		wantImp     *openrtb2.Imp
+		wantError   bool
+	}{
+		{
+			description: "nil",
+			imp:         nil,
+			fpd:         nil,
+			wantImp:     nil,
+			wantError:   true,
+		},
+		{
+			description: "nil_fpd",
+			imp:         imp1,
+			fpd:         nil,
+			wantImp:     imp1,
+			wantError:   true,
+		},
+		{
+			description: "empty_fpd",
+			imp:         imp1,
+			fpd:         json.RawMessage(`{}`),
+			wantImp:     imp1,
+			wantError:   false,
+		},
+		{
+			description: "nil_imp",
+			imp:         nil,
+			fpd:         json.RawMessage(`{}`),
+			wantImp:     nil,
+			wantError:   true,
+		},
+		{
+			description: "zero_value_imp",
+			imp:         &openrtb2.Imp{},
+			fpd:         json.RawMessage(`{}`),
+			wantImp:     &openrtb2.Imp{},
+			wantError:   false,
+		},
+		{
+			description: "invalid_json_on_existing_imp",
+			imp: &openrtb2.Imp{
+				Ext: json.RawMessage(`malformed`),
+			},
+			fpd: json.RawMessage(`{"ext": {"a":1}}`),
+			wantImp: &openrtb2.Imp{
+				Ext: json.RawMessage(`malformed`),
+			},
+			wantError: true,
+		},
+		{
+			description: "invalid_json_in_fpd",
+			imp: &openrtb2.Imp{
+				Ext: json.RawMessage(`{"ext": {"a":1}}`),
+			},
+			fpd: json.RawMessage(`malformed`),
+			wantImp: &openrtb2.Imp{
+				Ext: json.RawMessage(`{"ext": {"a":1}}`),
+			},
+			wantError: true,
+		},
+		{
+			description: "override_everything",
+			imp: &openrtb2.Imp{
+				ID:     "id1",
+				Metric: []openrtb2.Metric{{Type: "type1", Value: 1, Vendor: "vendor1"}},
+				Banner: &openrtb2.Banner{
+					W: ptrutil.ToPtr[int64](1),
+					H: ptrutil.ToPtr[int64](2),
+					Format: []openrtb2.Format{
+						{
+							W:   10,
+							H:   20,
+							Ext: json.RawMessage(`{"formatkey1":"formatval1"}`),
+						},
+					},
+				},
+				Instl:    1,
+				BidFloor: 1,
+				Ext:      json.RawMessage(`{"cool":"test"}`),
+			},
+			fpd: json.RawMessage(`{"id": "id2", "metric": [{"type":"type2", "value":2, "vendor":"vendor2"}], "banner": {"w":100, "h": 200, "format": [{"w":1000, "h":2000, "ext":{"formatkey1":"formatval2"}}]}, "instl":2, "bidfloor":2, "ext":{"cool":"test2"} }`),
+			wantImp: &openrtb2.Imp{
+				ID:     "id2",
+				Metric: []openrtb2.Metric{{Type: "type2", Value: 2, Vendor: "vendor2"}},
+				Banner: &openrtb2.Banner{
+					W: ptrutil.ToPtr[int64](100),
+					H: ptrutil.ToPtr[int64](200),
+					Format: []openrtb2.Format{
+						{
+							W:   1000,
+							H:   2000,
+							Ext: json.RawMessage(`{"formatkey1":"formatval2"}`),
+						},
+					},
+				},
+				Instl:    2,
+				BidFloor: 2,
+				Ext:      json.RawMessage(`{"cool":"test2"}`),
+			},
+		},
+		{
+			description: "override_partial_simple",
+			imp:         imp1,
+			fpd:         json.RawMessage(`{"id": "456", "banner": {"format": [{"w":1, "h":2}]} }`),
+			wantImp: &openrtb2.Imp{
+				ID: "456",
+				Banner: &openrtb2.Banner{
+					W: ptrutil.ToPtr[int64](200),
+					H: ptrutil.ToPtr[int64](400),
+					Format: []openrtb2.Format{
+						{
+							W: 1,
+							H: 2,
+						},
+					},
+				},
+			},
+		},
+		{
+			description: "override_partial_complex",
+			imp: &openrtb2.Imp{
+				ID:     "id1",
+				Metric: []openrtb2.Metric{{Type: "type1", Value: 1, Vendor: "vendor1"}},
+				Banner: &openrtb2.Banner{
+					W: ptrutil.ToPtr[int64](1),
+					H: ptrutil.ToPtr[int64](2),
+					Format: []openrtb2.Format{
+						{
+							W:   10,
+							H:   20,
+							Ext: json.RawMessage(`{"formatkey1":"formatval1"}`),
+						},
+					},
+				},
+				Instl:        1,
+				TagID:        "tag1",
+				BidFloor:     1,
+				Rwdd:         1,
+				DT:           1,
+				IframeBuster: []string{"buster1", "buster2"},
+				Ext:          json.RawMessage(`{"cool1":"test1", "cool2":"test2"}`),
+			},
+			fpd: json.RawMessage(`{"id": "id2", "metric": [{"type":"type2", "value":2, "vendor":"vendor2"}], "banner": {"w":100, "format": [{"w":1000, "h":2000, "ext":{"formatkey1":"formatval11"}}]}, "instl":2, "bidfloor":2, "ext":{"cool1":"test11"} }`),
+			wantImp: &openrtb2.Imp{
+				ID:     "id2",
+				Metric: []openrtb2.Metric{{Type: "type2", Value: 2, Vendor: "vendor2"}},
+				Banner: &openrtb2.Banner{
+					W: ptrutil.ToPtr[int64](100),
+					H: ptrutil.ToPtr[int64](2),
+					Format: []openrtb2.Format{
+						{
+							W:   1000,
+							H:   2000,
+							Ext: json.RawMessage(`{"formatkey1":"formatval11"}`),
+						},
+					},
+				},
+				Instl:        2,
+				TagID:        "tag1",
+				BidFloor:     2,
+				Rwdd:         1,
+				DT:           1,
+				IframeBuster: []string{"buster1", "buster2"},
+				Ext:          json.RawMessage(`{"cool1":"test11","cool2":"test2"}`),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			err := mergeImpFPD(test.imp, test.fpd, 1)
+			assert.Equal(t, test.wantImp, test.imp)
+
+			if test.wantError {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+		})
+	}
+}
+
 func TestCreateSanitizedImpExt(t *testing.T) {
 	testCases := []struct {
 		description       string
@@ -475,7 +764,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 			hostSChainNode:    nil,
 			bidderInfo:        config.BidderInfos{},
 		}
-		bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{})
 		if test.hasError {
 			assert.NotNil(t, err, "Error shouldn't be nil")
 		} else {
@@ -541,7 +830,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{})
 		assert.Empty(t, err, "No errors should be returned")
 		for _, bidderRequest := range bidderRequests {
 			bidderName := bidderRequest.BidderName
@@ -675,7 +964,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
 						W: ptrutil.ToPtr[int64](300),
 						H: ptrutil.ToPtr[int64](250),
 					},
-					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`),
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`),
 				},
 			},
 			expectedBidderRequests: map[string]BidderRequest{
@@ -706,7 +995,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
 						W: ptrutil.ToPtr[int64](300),
 						H: ptrutil.ToPtr[int64](250),
 					},
-					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`),
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`),
 				},
 				{
 					ID:  "imp-id2",
@@ -746,7 +1035,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
 						W: ptrutil.ToPtr[int64](300),
 						H: ptrutil.ToPtr[int64](250),
 					},
-					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`),
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"},"bidderB":{"placementId":"456"}}}}`),
 				},
 				{
 					ID:  "imp-id2",
@@ -812,11 +1101,11 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
 			imps: []openrtb2.Imp{
 				{
 					ID:  "imp-id1",
-					Ext: json.RawMessage(`"prebid": {}`),
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`),
 				},
 				{
 					ID:  "imp-id2",
-					Ext: json.RawMessage(`"prebid": {}`),
+					Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`),
 				},
 			},
 			expectedBidderRequests: map[string]BidderRequest{
@@ -856,7 +1145,7 @@ func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+		actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{})
 		assert.Empty(t, err, "No errors should be returned")
 		assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description)
 		for _, actualBidderRequest := range actualBidderRequests {
@@ -987,7 +1276,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 		req := newBidRequest(t)
 		req.Ext = test.reqExt
 		req.Regs = &openrtb2.Regs{
-			Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`),
+			USPrivacy: test.ccpaConsent,
 		}
 
 		privacyConfig := config.Privacy{
@@ -1015,26 +1304,31 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			},
 		}.Builder
 
+		metricsMock := metrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
+
 		bidderToSyncerKey := map[string]string{}
 		reqSplitter := &requestSplitter{
 			bidderToSyncerKey: bidderToSyncerKey,
-			me:                &metrics.MetricsEngineMock{},
+			me:                &metricsMock,
 			privacyConfig:     privacyConfig,
 			gdprPermsBuilder:  gdprPermissionsBuilder,
 			hostSChainNode:    nil,
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{})
 		result := bidderRequests[0]
 
 		assert.Nil(t, errs)
 		if test.expectDataScrub {
 			assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		} else {
 			assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		}
 		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
@@ -1042,32 +1336,32 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 
 func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) {
 	testCases := []struct {
-		description string
-		reqExt      json.RawMessage
-		reqRegsExt  json.RawMessage
-		expectError error
+		description    string
+		reqExt         json.RawMessage
+		reqRegsPrivacy string
+		expectError    error
 	}{
 		{
-			description: "Invalid Consent",
-			reqExt:      json.RawMessage(`{"prebid":{"nosale":["*"]}}`),
-			reqRegsExt:  json.RawMessage(`{"us_privacy":"malformed"}`),
+			description:    "Invalid Consent",
+			reqExt:         json.RawMessage(`{"prebid":{"nosale":["*"]}}`),
+			reqRegsPrivacy: "malformed",
 			expectError: &errortypes.Warning{
 				Message:     "request.regs.ext.us_privacy must contain 4 characters",
 				WarningCode: errortypes.InvalidPrivacyConsentWarningCode,
 			},
 		},
 		{
-			description: "Invalid No Sale Bidders",
-			reqExt:      json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`),
-			reqRegsExt:  json.RawMessage(`{"us_privacy":"1NYN"}`),
-			expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"),
+			description:    "Invalid No Sale Bidders",
+			reqExt:         json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`),
+			reqRegsPrivacy: "1NYN",
+			expectError:    errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"),
 		},
 	}
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
 		req.Ext = test.reqExt
-		req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt}
+		req.Regs = &openrtb2.Regs{USPrivacy: test.reqRegsPrivacy}
 
 		var reqExtStruct openrtb_ext.ExtRequest
 		err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct)
@@ -1102,7 +1396,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		_, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, map[string]float64{})
+		_, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, false, map[string]float64{})
 
 		assert.ElementsMatch(t, []error{test.expectError}, errs, test.description)
 	}
@@ -1161,7 +1455,7 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{})
 		result := bidderRequests[0]
 
 		assert.Nil(t, errs)
@@ -1183,46 +1477,114 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
 	testCases := []struct {
 		description   string
 		inExt         json.RawMessage
-		inSourceExt   json.RawMessage
+		inSChain      *openrtb2.SupplyChain
 		outRequestExt json.RawMessage
-		outSourceExt  json.RawMessage
+		outSource     *openrtb2.Source
 		hasError      bool
+		ortbVersion   string
 	}{
 		{
 			description:   "nil",
 			inExt:         nil,
-			inSourceExt:   nil,
+			inSChain:      nil,
 			outRequestExt: nil,
-			outSourceExt:  nil,
+			outSource: &openrtb2.Source{
+				TID:    "testTID",
+				SChain: nil,
+				Ext:    nil,
+			},
 		},
 		{
-			description:   "ORTB 2.5 chain at source.ext.schain",
-			inExt:         nil,
-			inSourceExt:   json.RawMessage(`{` + seller1SChain + `}`),
+			description: "Supply Chain defined in request.Source.supplyChain",
+			inExt:       nil,
+			inSChain: &openrtb2.SupplyChain{
+				Complete: 1,
+				Ver:      "1.0",
+				Ext:      nil,
+				Nodes: []openrtb2.SupplyChainNode{
+					{
+						ASI: "directseller1.com",
+						SID: "00001",
+						RID: "BidRequest1",
+						HP:  openrtb2.Int8Ptr(1),
+						Ext: nil,
+					},
+				},
+			},
 			outRequestExt: nil,
-			outSourceExt:  json.RawMessage(`{` + seller1SChain + `}`),
+			outSource: &openrtb2.Source{
+				TID: "testTID",
+				SChain: &openrtb2.SupplyChain{
+					Complete: 1,
+					Ver:      "1.0",
+					Ext:      nil,
+					Nodes: []openrtb2.SupplyChainNode{
+						{
+							ASI: "directseller1.com",
+							SID: "00001",
+							RID: "BidRequest1",
+							HP:  openrtb2.Int8Ptr(1),
+							Ext: nil,
+						},
+					},
+				},
+				Ext: nil,
+			},
+			ortbVersion: "2.6",
 		},
 		{
-			description:   "ORTB 2.5 schain at request.ext.prebid.schains",
+			description:   "Supply Chain defined in request.ext.prebid.schains",
 			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-			inSourceExt:   nil,
+			inSChain:      nil,
 			outRequestExt: nil,
-			outSourceExt:  json.RawMessage(`{` + seller1SChain + `}`),
+			outSource: &openrtb2.Source{
+				TID: "testTID",
+				SChain: &openrtb2.SupplyChain{
+					Complete: 1,
+					Ver:      "1.0",
+					Ext:      nil,
+					Nodes: []openrtb2.SupplyChainNode{
+						{
+							ASI: "directseller1.com",
+							SID: "00001",
+							RID: "BidRequest1",
+							HP:  openrtb2.Int8Ptr(1),
+							Ext: nil,
+						},
+					},
+				},
+				Ext: nil,
+			},
+			ortbVersion: "2.6",
 		},
 		{
-			description:   "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.",
-			inExt:         json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
-			inSourceExt:   json.RawMessage(`{` + seller1SChain + `}`),
+			description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.",
+			inExt:       json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+			inSChain: &openrtb2.SupplyChain{
+				Complete: 1,
+				Ver:      "1.0",
+				Ext:      nil,
+				Nodes: []openrtb2.SupplyChainNode{
+					{
+						ASI: "directseller1.com",
+						SID: "00001",
+						RID: "BidRequest1",
+						HP:  openrtb2.Int8Ptr(1),
+						Ext: nil,
+					},
+				},
+			},
+
 			outRequestExt: nil,
-			outSourceExt:  nil,
+			outSource:     nil,
 			hasError:      true,
 		},
 	}
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
-		if test.inSourceExt != nil {
-			req.Source.Ext = test.inSourceExt
+		if test.inSChain != nil {
+			req.Source.SChain = test.inSChain
 		}
 
 		var extRequest *openrtb_ext.ExtRequest
@@ -1251,17 +1613,17 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
 			privacyConfig:     config.Privacy{},
 			gdprPermsBuilder:  gdprPermissionsBuilder,
 			hostSChainNode:    nil,
-			bidderInfo:        config.BidderInfos{},
+			bidderInfo:        config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}},
 		}
 
-		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{})
 		if test.hasError == true {
 			assert.NotNil(t, errs)
 			assert.Len(t, bidderRequests, 0)
 		} else {
 			result := bidderRequests[0]
 			assert.Nil(t, errs)
-			assert.Equal(t, test.outSourceExt, result.BidRequest.Source.Ext, test.description+":Source.Ext")
+			assert.Equal(t, test.outSource, result.BidRequest.Source, test.description+":Source")
 			assert.Equal(t, test.outRequestExt, result.BidRequest.Ext, test.description+":Ext")
 		}
 	}
@@ -1325,7 +1687,7 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{})
 		if test.hasError == true {
 			assert.NotNil(t, errs)
 			assert.Len(t, bidderRequests, 0)
@@ -1917,7 +2279,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+		results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{})
 		result := results[0]
 
 		assert.Nil(t, errs)
@@ -1934,160 +2296,57 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 
 func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 	tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA"
-	trueValue, falseValue := true, false
 
 	testCases := []struct {
 		description         string
-		gdprAccountEnabled  *bool
-		gdprHostEnabled     bool
-		gdpr                string
 		gdprConsent         string
 		gdprScrub           bool
+		gdprSignal          gdpr.Signal
+		gdprEnforced        bool
 		permissionsError    error
-		gdprDefaultValue    string
 		expectPrivacyLabels metrics.PrivacyLabels
 		expectError         bool
 	}{
 		{
-			description:        "Enforce - TCF Invalid",
-			gdprAccountEnabled: &trueValue,
-			gdprHostEnabled:    true,
-			gdpr:               "1",
-			gdprConsent:        "malformed",
-			gdprScrub:          false,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   true,
-				GDPRTCFVersion: "",
-			},
-		},
-		{
-			description:        "Enforce",
-			gdprAccountEnabled: &trueValue,
-			gdprHostEnabled:    true,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   true,
-				GDPRTCFVersion: metrics.TCFVersionV2,
-			},
-		},
-		{
-			description:        "Not Enforce",
-			gdprAccountEnabled: &trueValue,
-			gdprHostEnabled:    true,
-			gdpr:               "0",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          false,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   false,
-				GDPRTCFVersion: "",
-			},
-		},
-		{
-			description:        "Enforce; GDPR signal extraction error",
-			gdprAccountEnabled: &trueValue,
-			gdprHostEnabled:    true,
-			gdpr:               "0{",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   true,
-				GDPRTCFVersion: metrics.TCFVersionV2,
-			},
-			expectError: true,
-		},
-		{
-			description:        "Enforce; account GDPR enabled, host GDPR setting disregarded",
-			gdprAccountEnabled: &trueValue,
-			gdprHostEnabled:    false,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   true,
-				GDPRTCFVersion: metrics.TCFVersionV2,
-			},
-		},
-		{
-			description:        "Not Enforce; account GDPR disabled, host GDPR setting disregarded",
-			gdprAccountEnabled: &falseValue,
-			gdprHostEnabled:    true,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          false,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   false,
-				GDPRTCFVersion: "",
-			},
-		},
-		{
-			description:        "Enforce; account GDPR not specified, host GDPR enabled",
-			gdprAccountEnabled: nil,
-			gdprHostEnabled:    true,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			gdprDefaultValue:   "1",
+			description:  "enforce no scrub - TCF invalid",
+			gdprConsent:  "malformed",
+			gdprScrub:    false,
+			gdprSignal:   gdpr.SignalYes,
+			gdprEnforced: true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
-				GDPRTCFVersion: metrics.TCFVersionV2,
-			},
-		},
-		{
-			description:        "Not Enforce; account GDPR not specified, host GDPR disabled",
-			gdprAccountEnabled: nil,
-			gdprHostEnabled:    false,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          false,
-			gdprDefaultValue:   "1",
-			expectPrivacyLabels: metrics.PrivacyLabels{
-				GDPREnforced:   false,
 				GDPRTCFVersion: "",
 			},
 		},
 		{
-			description:        "Enforce - Ambiguous signal, don't sync user if ambiguous",
-			gdprAccountEnabled: nil,
-			gdprHostEnabled:    true,
-			gdpr:               "null",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			gdprDefaultValue:   "1",
+			description:  "enforce and scrub",
+			gdprConsent:  tcf2Consent,
+			gdprScrub:    true,
+			gdprSignal:   gdpr.SignalYes,
+			gdprEnforced: true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
 				GDPRTCFVersion: metrics.TCFVersionV2,
 			},
 		},
 		{
-			description:        "Not Enforce - Ambiguous signal, sync user if ambiguous",
-			gdprAccountEnabled: nil,
-			gdprHostEnabled:    true,
-			gdpr:               "null",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          false,
-			gdprDefaultValue:   "0",
+			description:  "not enforce",
+			gdprConsent:  tcf2Consent,
+			gdprScrub:    false,
+			gdprSignal:   gdpr.SignalYes,
+			gdprEnforced: false,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   false,
 				GDPRTCFVersion: "",
 			},
 		},
 		{
-			description:        "Enforce - error while checking if personal info is allowed",
-			gdprAccountEnabled: nil,
-			gdprHostEnabled:    true,
-			gdpr:               "1",
-			gdprConsent:        tcf2Consent,
-			gdprScrub:          true,
-			permissionsError:   errors.New("Some error"),
-			gdprDefaultValue:   "1",
+			description:      "enforce - error while checking if personal info is allowed",
+			gdprConsent:      tcf2Consent,
+			gdprScrub:        true,
+			permissionsError: errors.New("Some error"),
+			gdprSignal:       gdpr.SignalYes,
+			gdprEnforced:     true,
 			expectPrivacyLabels: metrics.PrivacyLabels{
 				GDPREnforced:   true,
 				GDPRTCFVersion: metrics.TCFVersionV2,
@@ -2097,25 +2356,10 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 
 	for _, test := range testCases {
 		req := newBidRequest(t)
-		req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`)
-		req.Regs = &openrtb2.Regs{
-			Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`),
-		}
-
-		privacyConfig := config.Privacy{
-			GDPR: config.GDPR{
-				DefaultValue: test.gdprDefaultValue,
-				TCF2: config.TCF2{
-					Enabled: test.gdprHostEnabled,
-				},
-			},
-		}
+		req.User.Consent = test.gdprConsent
 
-		accountConfig := config.Account{
-			GDPR: config.AccountGDPR{
-				Enabled: test.gdprAccountEnabled,
-			},
-		}
+		privacyConfig := config.Privacy{}
+		accountConfig := config.Account{}
 
 		auctionReq := AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req},
@@ -2136,21 +2380,19 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			},
 		}.Builder
 
-		gdprDefaultValue := gdpr.SignalYes
-		if test.gdprDefaultValue == "0" {
-			gdprDefaultValue = gdpr.SignalNo
-		}
+		metricsMock := metrics.MetricsEngineMock{}
+		metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
 
 		reqSplitter := &requestSplitter{
 			bidderToSyncerKey: map[string]string{},
-			me:                &metrics.MetricsEngineMock{},
+			me:                &metricsMock,
 			privacyConfig:     privacyConfig,
 			gdprPermsBuilder:  gdprPermissionsBuilder,
 			hostSChainNode:    nil,
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdprDefaultValue, map[string]float64{})
+		results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, test.gdprSignal, test.gdprEnforced, map[string]float64{})
 		result := results[0]
 
 		if test.expectError {
@@ -2162,9 +2404,11 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 		if test.gdprScrub {
 			assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		} else {
 			assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID")
 			assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5")
+			metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
 		}
 		assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
 	}
@@ -2208,15 +2452,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 		}
 		req.Imp[0].Ext = json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "rubicon": {}}}}`)
 
-		privacyConfig := config.Privacy{
-			GDPR: config.GDPR{
-				DefaultValue: "0",
-				TCF2: config.TCF2{
-					Enabled: test.gdprEnforced,
-				},
-			},
-		}
-
+		privacyConfig := config.Privacy{}
 		accountConfig := config.Account{
 			GDPR: config.AccountGDPR{
 				Enabled: nil,
@@ -2251,7 +2487,7 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+		results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalYes, test.gdprEnforced, map[string]float64{})
 
 		// extract bidder name from each request in the results
 		bidders := []openrtb_ext.BidderName{}
@@ -2302,14 +2538,14 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) {
 			req:         AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config},
 			expectRegs:  &downgradedRegs,
 			expectUser:  &downgradedUser,
-			bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false}}},
+			bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false, Version: "2.6"}}},
 		},
 		{
 			name:        "Supported",
 			req:         AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config},
 			expectRegs:  bidReq.Regs,
 			expectUser:  bidReq.User,
-			bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true}}},
+			bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true, Version: "2.6"}}},
 		},
 	}
 
@@ -2339,7 +2575,7 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) {
 				hostSChainNode:    nil,
 				bidderInfo:        test.bidderInfos,
 			}
-			bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, map[string]float64{})
+			bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{})
 			assert.Nil(t, err, "Err should be nil")
 			bidRequest := bidderRequests[0]
 			assert.Equal(t, test.expectRegs, bidRequest.BidRequest.Regs)
@@ -2356,145 +2592,146 @@ func TestBuildRequestExtForBidder(t *testing.T) {
 	)
 
 	testCases := []struct {
-		description          string
+		name                 string
 		requestExt           json.RawMessage
 		bidderParams         map[string]json.RawMessage
 		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
 		expectedJson         json.RawMessage
 	}{
 		{
-			description:          "Nil",
+			name:                 "Nil",
 			bidderParams:         nil,
 			requestExt:           nil,
 			alternateBidderCodes: nil,
 			expectedJson:         nil,
 		},
 		{
-			description:          "Empty",
+			name:                 "Empty",
 			bidderParams:         nil,
 			alternateBidderCodes: nil,
 			requestExt:           json.RawMessage(`{}`),
 			expectedJson:         nil,
 		},
 		{
-			description:  "Prebid - Allowed Fields Only",
+			name:         "Prebid - Allowed Fields Only",
 			bidderParams: nil,
 			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`),
 			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`),
 		},
 		{
-			description:  "Prebid - Allowed Fields + Bidder Params",
+			name:         "Prebid - Allowed Fields + Bidder Params",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`),
 			expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Other",
+			name:         "Other",
 			bidderParams: nil,
 			requestExt:   json.RawMessage(`{"other":"foo"}`),
 			expectedJson: json.RawMessage(`{"other":"foo"}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params",
+			name:         "Prebid + Other + Bider Params",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`),
 		},
 		{
-			description:          "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config",
+			name:                 "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config",
 			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
 			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}},
 			requestExt:           json.RawMessage(`{"other":"foo"}`),
 			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`),
 		},
 		{
-			description:          "Prebid + AlternateBidderCodes in request",
+			name:                 "Prebid + AlternateBidderCodes in request",
 			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
 			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{},
 			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
 			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`),
 		},
 		{
-			description:          "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config",
+			name:                 "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config",
 			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
 			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{},
 			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
 			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`),
 		},
 		{
-			description:          "Prebid + AlternateBidderCodes in both pbs config and in the request",
+			name:                 "Prebid + AlternateBidderCodes in both pbs config and in the request",
 			bidderParams:         map[string]json.RawMessage{bidder: bidderParams},
 			alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}},
 			requestExt:           json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`),
 			expectedJson:         json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params + MultiBid.Bidder",
+			name:         "Prebid + Other + Bider Params + MultiBid.Bidder",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"}],"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params + MultiBid.Bidders",
+			name:         "Prebid + Other + Bider Params + MultiBid.Bidders",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"pubmatic","maxbids":3,"targetbiddercodeprefix":"pubM"},{"bidders":["foo","groupm"],"maxbids":4}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidders":["foo"],"maxbids":4}],"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)",
+			name:         "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)",
+			name:         "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)",
 			bidderParams: map[string]json.RawMessage{bidder: bidderParams},
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`),
 		},
 		{
-			description:  "Prebid + AlternateBidderCodes.MultiBid.Bidder",
+			name:         "Prebid + AlternateBidderCodes.MultiBid.Bidder",
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`),
 		},
 		{
-			description:  "Prebid + AlternateBidderCodes.MultiBid.Bidders",
+			name:         "Prebid + AlternateBidderCodes.MultiBid.Bidders",
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4}]}}`),
 		},
 		{
-			description:  "Prebid + AlternateBidderCodes.MultiBid.Bidder with *",
+			name:         "Prebid + AlternateBidderCodes.MultiBid.Bidder with *",
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`),
 		},
 		{
-			description:  "Prebid + AlternateBidderCodes.MultiBid.Bidders with *",
+			name:         "Prebid + AlternateBidderCodes.MultiBid.Bidders with *",
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4},{"bidders":["groupm"],"maxbids":4}]}}`),
 		},
 		{
-			description:  "Prebid + AlternateBidderCodes + MultiBid",
+			name:         "Prebid + AlternateBidderCodes + MultiBid",
 			requestExt:   json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"multibid":[{"bidder":"foo3","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`),
 			expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}}}}`),
 		},
 	}
 
 	for _, test := range testCases {
-		requestExtParsed := &openrtb_ext.ExtRequest{}
-		if test.requestExt != nil {
-			err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed)
-			if !assert.NoError(t, err, test.description+":parse_ext") {
-				continue
+		t.Run(test.name, func(t *testing.T) {
+			req := openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: test.requestExt,
+				},
 			}
-		}
+			err := buildRequestExtForBidder(bidder, &req, test.bidderParams, test.alternateBidderCodes)
+			assert.NoError(t, req.RebuildRequest())
+			assert.NoError(t, err)
 
-		actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes)
-		if len(test.expectedJson) > 0 {
-			assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json")
-		} else {
-			assert.Equal(t, test.expectedJson, actualJson, test.description+":json")
-		}
-		assert.NoError(t, actualErr, test.description+":err")
+			if len(test.expectedJson) > 0 {
+				assert.JSONEq(t, string(test.expectedJson), string(req.Ext))
+			} else {
+				assert.Equal(t, test.expectedJson, req.Ext)
+			}
+		})
 	}
 }
 
@@ -2502,28 +2739,37 @@ func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) {
 	var (
 		bidder               = "foo"
 		requestExt           = json.RawMessage(`{}`)
-		requestExtParsed     *openrtb_ext.ExtRequest
 		bidderParams         map[string]json.RawMessage
 		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
 	)
 
-	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes)
-	assert.Nil(t, actualJson)
-	assert.NoError(t, actualErr)
+	req := openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			Ext: requestExt,
+		},
+	}
+	err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes)
+	assert.NoError(t, req.RebuildRequest())
+	assert.Nil(t, req.Ext)
+	assert.NoError(t, err)
 }
 
 func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) {
 	var (
 		bidder               = "foo"
 		requestExt           = json.RawMessage(`malformed`)
-		requestExtParsed     = &openrtb_ext.ExtRequest{}
 		bidderParams         map[string]json.RawMessage
 		alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes
 	)
 
-	actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes)
-	assert.Equal(t, json.RawMessage(nil), actualJson)
-	assert.EqualError(t, actualErr, "expect { or n, but found m")
+	req := openrtb_ext.RequestWrapper{
+		BidRequest: &openrtb2.BidRequest{
+			Ext: requestExt,
+		},
+	}
+	err := buildRequestExtForBidder(bidder, &req, bidderParams, alternateBidderCodes)
+	assert.NoError(t, req.RebuildRequest())
+	assert.EqualError(t, err, "expect { or n, but found m")
 }
 
 // newAdapterAliasBidRequest builds a BidRequest with aliases
@@ -2709,193 +2955,112 @@ func TestRemoveUnpermissionedEids(t *testing.T) {
 	bidder := "bidderA"
 
 	testCases := []struct {
-		description     string
-		userExt         json.RawMessage
-		eidPermissions  []openrtb_ext.ExtRequestPrebidDataEidPermission
-		expectedUserExt json.RawMessage
+		description      string
+		userEids         []openrtb2.EID
+		eidPermissions   []openrtb_ext.ExtRequestPrebidDataEidPermission
+		expectedUserEids []openrtb2.EID
 	}{
-		{
-			description: "Extension Nil",
-			userExt:     nil,
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"bidderA"}},
-			},
-			expectedUserExt: nil,
-		},
-		{
-			description: "Extension Empty",
-			userExt:     json.RawMessage(`{}`),
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"bidderA"}},
-			},
-			expectedUserExt: json.RawMessage(`{}`),
-		},
-		{
-			description: "Extension Empty - Keep Other Data",
-			userExt:     json.RawMessage(`{"other":42}`),
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"bidderA"}},
-			},
-			expectedUserExt: json.RawMessage(`{"other":42}`),
-		},
+
 		{
 			description: "Eids Empty",
-			userExt:     json.RawMessage(`{"eids":[]}`),
+			userEids:    []openrtb2.EID{},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"bidderA"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[]}`),
+			expectedUserEids: []openrtb2.EID{},
 		},
 		{
-			description: "Eids Empty - Keep Other Data",
-			userExt:     json.RawMessage(`{"eids":[],"other":42}`),
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"bidderA"}},
-			},
-			expectedUserExt: json.RawMessage(`{"eids":[],"other":42}`),
-		},
-		{
-			description:     "Allowed By Nil Permissions",
-			userExt:         json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
-			eidPermissions:  nil,
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			description:      "Allowed By Nil Permissions",
+			userEids:         []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
+			eidPermissions:   nil,
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
-			description:     "Allowed By Empty Permissions",
-			userExt:         json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
-			eidPermissions:  []openrtb_ext.ExtRequestPrebidDataEidPermission{},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			description:      "Allowed By Empty Permissions",
+			userEids:         []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
+			eidPermissions:   []openrtb_ext.ExtRequestPrebidDataEidPermission{},
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
 			description: "Allowed By Specific Bidder",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			userEids:    []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"bidderA"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
 			description: "Allowed By Specific Bidder - Case Insensitive",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			userEids:    []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"BIDDERA"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
 			description: "Allowed By All Bidders",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			userEids:    []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"*"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
 			description: "Allowed By Lack Of Matching Source",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			userEids:    []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source2", Bidders: []string{"otherBidder"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
-		},
-		{
-			description: "Allowed - Keep Other Data",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`),
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"bidderA"}},
-			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`),
+			expectedUserEids: []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 		},
 		{
 			description: "Denied",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`),
+			userEids:    []openrtb2.EID{{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID"}}}},
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"otherBidder"}},
 			},
-			expectedUserExt: nil,
+			expectedUserEids: nil,
 		},
 		{
-			description: "Denied - Keep Other Data",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"otherdata":42}`),
-			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-				{Source: "source1", Bidders: []string{"otherBidder"}},
+			description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied",
+			userEids: []openrtb2.EID{
+				{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}},
+				{Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}},
+				{Source: "source3", UIDs: []openrtb2.UID{{ID: "anyID3"}}},
 			},
-			expectedUserExt: json.RawMessage(`{"otherdata":42}`),
-		},
-		{
-			description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data",
-			userExt:     json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]},{"source":"source3","uids":[{"id":"anyID3"}]}],"other":42}`),
 			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
 				{Source: "source1", Bidders: []string{"bidderA"}},
 				{Source: "source3", Bidders: []string{"otherBidder"}},
 			},
-			expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]}],"other":42}`),
+			expectedUserEids: []openrtb2.EID{
+				{Source: "source1", UIDs: []openrtb2.UID{{ID: "anyID1"}}},
+				{Source: "source2", UIDs: []openrtb2.UID{{ID: "anyID2"}}},
+			},
 		},
 	}
 
 	for _, test := range testCases {
-		request := &openrtb2.BidRequest{
-			User: &openrtb2.User{Ext: test.userExt},
-		}
+		t.Run(test.description, func(t *testing.T) {
+			request := &openrtb2.BidRequest{
+				User: &openrtb2.User{EIDs: test.userEids},
+			}
 
-		requestExt := &openrtb_ext.ExtRequest{
-			Prebid: openrtb_ext.ExtRequestPrebid{
+			reqWrapper := openrtb_ext.RequestWrapper{BidRequest: request}
+			re, _ := reqWrapper.GetRequestExt()
+			re.SetPrebid(&openrtb_ext.ExtRequestPrebid{
 				Data: &openrtb_ext.ExtRequestPrebidData{
 					EidPermissions: test.eidPermissions,
 				},
-			},
-		}
-
-		expectedRequest := &openrtb2.BidRequest{
-			User: &openrtb2.User{Ext: test.expectedUserExt},
-		}
-
-		resultErr := removeUnpermissionedEids(request, bidder, requestExt)
-		assert.NoError(t, resultErr, test.description)
-		assert.Equal(t, expectedRequest, request, test.description)
-	}
-}
+			})
 
-func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) {
-	testCases := []struct {
-		description string
-		userExt     json.RawMessage
-		expectedErr string
-	}{
-		{
-			description: "Malformed Ext",
-			userExt:     json.RawMessage(`malformed`),
-			expectedErr: "expect { or n, but found m",
-		},
-		{
-			description: "Malformed Eid Array Type",
-			userExt:     json.RawMessage(`{"eids":[42]}`),
-			expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4",
-		},
-		{
-			description: "Malformed Eid Item Type",
-			userExt:     json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`),
-			expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4",
-		},
-	}
-
-	for _, test := range testCases {
-		request := &openrtb2.BidRequest{
-			User: &openrtb2.User{Ext: test.userExt},
-		}
-
-		requestExt := &openrtb_ext.ExtRequest{
-			Prebid: openrtb_ext.ExtRequestPrebid{
-				Data: &openrtb_ext.ExtRequestPrebidData{
-					EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-						{Source: "source1", Bidders: []string{"*"}},
-					},
-				},
-			},
-		}
+			expectedRequest := &openrtb2.BidRequest{
+				User: &openrtb2.User{EIDs: test.expectedUserEids},
+			}
 
-		resultErr := removeUnpermissionedEids(request, "bidderA", requestExt)
-		assert.EqualError(t, resultErr, test.expectedErr, test.description)
+			resultErr := removeUnpermissionedEids(&reqWrapper, bidder)
+			assert.NoError(t, resultErr, test.description)
+			assert.Equal(t, expectedRequest, reqWrapper.BidRequest)
+		})
 	}
 }
 
@@ -3013,23 +3178,17 @@ func TestGetDebugInfo(t *testing.T) {
 
 func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) {
 	testCases := []struct {
-		description string
-		request     *openrtb2.BidRequest
-		requestExt  *openrtb_ext.ExtRequest
+		description    string
+		request        *openrtb2.BidRequest
+		eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission
 	}{
 		{
 			description: "Nil User",
 			request: &openrtb2.BidRequest{
 				User: nil,
 			},
-			requestExt: &openrtb_ext.ExtRequest{
-				Prebid: openrtb_ext.ExtRequestPrebid{
-					Data: &openrtb_ext.ExtRequestPrebidData{
-						EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-							{Source: "source1", Bidders: []string{"*"}},
-						},
-					},
-				},
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"*"}},
 			},
 		},
 		{
@@ -3037,14 +3196,8 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) {
 			request: &openrtb2.BidRequest{
 				User: &openrtb2.User{},
 			},
-			requestExt: &openrtb_ext.ExtRequest{
-				Prebid: openrtb_ext.ExtRequestPrebid{
-					Data: &openrtb_ext.ExtRequestPrebidData{
-						EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
-							{Source: "source1", Bidders: []string{"*"}},
-						},
-					},
-				},
+			eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
+				{Source: "source1", Bidders: []string{"*"}},
 			},
 		},
 		{
@@ -3052,27 +3205,25 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) {
 			request: &openrtb2.BidRequest{
 				User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)},
 			},
-			requestExt: nil,
-		},
-		{
-			description: "Nil Prebid Data",
-			request: &openrtb2.BidRequest{
-				User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)},
-			},
-			requestExt: &openrtb_ext.ExtRequest{
-				Prebid: openrtb_ext.ExtRequestPrebid{
-					Data: nil,
-				},
-			},
 		},
 	}
 
 	for _, test := range testCases {
-		requestExpected := *test.request
+		t.Run(test.description, func(t *testing.T) {
+			requestExpected := *test.request
+			reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request}
+
+			re, _ := reqWrapper.GetRequestExt()
+			re.SetPrebid(&openrtb_ext.ExtRequestPrebid{
+				Data: &openrtb_ext.ExtRequestPrebidData{
+					EidPermissions: test.eidPermissions,
+				},
+			})
 
-		resultErr := removeUnpermissionedEids(test.request, "bidderA", test.requestExt)
-		assert.NoError(t, resultErr, test.description+":err")
-		assert.Equal(t, &requestExpected, test.request, test.description+":request")
+			resultErr := removeUnpermissionedEids(&reqWrapper, "bidderA")
+			assert.NoError(t, resultErr, test.description+":err")
+			assert.Equal(t, &requestExpected, reqWrapper.BidRequest, test.description+":request")
+		})
 	}
 }
 
@@ -3107,28 +3258,57 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) {
 		},
 	}.Builder
 
+	ortb26enabled := config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: "2.6"}}
 	reqSplitter := &requestSplitter{
 		bidderToSyncerKey: map[string]string{},
 		me:                &metrics.MetricsEngineMock{},
 		privacyConfig:     config.Privacy{},
 		gdprPermsBuilder:  gdprPermissionsBuilder,
 		hostSChainNode:    nil,
-		bidderInfo:        config.BidderInfos{},
+		bidderInfo:        config.BidderInfos{"appnexus": ortb26enabled, "axonix": ortb26enabled},
 	}
-	bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{})
+	bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{})
 
 	assert.Nil(t, errs)
 	assert.Len(t, bidderRequests, 2, "Bid request count is not 2")
 
-	bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{}
+	bidRequestSourceSupplyChain := map[openrtb_ext.BidderName]*openrtb2.SupplyChain{}
 	for _, bidderRequest := range bidderRequests {
-		bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext
+		bidRequestSourceSupplyChain[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.SChain
+	}
+
+	appnexusSchainsSchainExpected := &openrtb2.SupplyChain{
+		Complete: 1,
+		Ver:      "1.0",
+		Ext:      nil,
+		Nodes: []openrtb2.SupplyChainNode{
+			{
+				ASI: "directseller1.com",
+				SID: "00001",
+				RID: "BidRequest1",
+				HP:  openrtb2.Int8Ptr(1),
+				Ext: nil,
+			},
+		},
 	}
 
-	appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`)
-	axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`)
-	assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext")
-	assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext")
+	axonixSchainsSchainExpected := &openrtb2.SupplyChain{
+		Complete: 1,
+		Ver:      "1.0",
+		Ext:      nil,
+		Nodes: []openrtb2.SupplyChainNode{
+			{
+				ASI: "directseller2.com",
+				SID: "00002",
+				RID: "BidRequest2",
+				HP:  openrtb2.Int8Ptr(1),
+				Ext: nil,
+			},
+		},
+	}
+
+	assert.Equal(t, appnexusSchainsSchainExpected, bidRequestSourceSupplyChain["appnexus"], "Incorrect appnexus bid request schain ")
+	assert.Equal(t, axonixSchainsSchainExpected, bidRequestSourceSupplyChain["axonix"], "Incorrect axonix bid request schain")
 }
 
 func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) {
@@ -3177,7 +3357,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) {
 			}},
 		},
 		{
-			description:        "bidAjustement Not provided",
+			description:        "bidAdjustment Not provided",
 			gdprAccountEnabled: &falseValue,
 			gdprHostEnabled:    true,
 			gdpr:               "1",
@@ -3219,6 +3399,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) {
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req},
 			UserSyncs:         &emptyUsersync{},
 			Account:           accountConfig,
+			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
 		}
 		gdprPermissionsBuilder := fakePermissionsBuilder{
 			permissions: &permissionsMock{
@@ -3235,7 +3416,7 @@ func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) {
 			hostSChainNode:    nil,
 			bidderInfo:        config.BidderInfos{},
 		}
-		results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, test.bidAdjustmentFactor)
+		results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, test.bidAdjustmentFactor)
 		result := results[0]
 		assert.Nil(t, errs)
 		assert.Equal(t, test.expectedImp, result.BidRequest.Imp, test.description)
@@ -3251,6 +3432,7 @@ func TestApplyFPD(t *testing.T) {
 		inputBidderIsRequestAlias bool
 		inputRequest              openrtb2.BidRequest
 		expectedRequest           openrtb2.BidRequest
+		fpdUserEIDsExisted        bool
 	}{
 		{
 			description:               "fpd-nil",
@@ -3372,77 +3554,122 @@ func TestApplyFPD(t *testing.T) {
 			inputRequest:              openrtb2.BidRequest{},
 			expectedRequest:           openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}},
 		},
+		{
+			description: "req.User is defined and had bidder fpd user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from fpd",
+			inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
+				"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}},
+			},
+			inputBidderName:           "bidderFromRequest",
+			inputBidderCoreName:       "bidderNormalized",
+			inputBidderIsRequestAlias: false,
+			inputRequest:              openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}},
+			expectedRequest:           openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}},
+			fpdUserEIDsExisted:        true,
+		},
+		{
+			description: "req.User is defined and doesn't have fpr user eids (fpdUserEIDsExisted); bidderFPD.User defined and has EIDs. Expect to see user.EIDs in result request taken from original req",
+			inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{
+				"bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source1"}, {Source: "source2"}}}},
+			},
+			inputBidderName:           "bidderFromRequest",
+			inputBidderCoreName:       "bidderNormalized",
+			inputBidderIsRequestAlias: false,
+			inputRequest:              openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}},
+			expectedRequest:           openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", EIDs: []openrtb2.EID{{Source: "source3"}, {Source: "source4"}}}},
+			fpdUserEIDsExisted:        false,
+		},
 	}
 
 	for _, testCase := range testCases {
-		bidderRequest := BidderRequest{
-			BidderName:     openrtb_ext.BidderName(testCase.inputBidderName),
-			BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName),
-			IsRequestAlias: testCase.inputBidderIsRequestAlias,
-			BidRequest:     &testCase.inputRequest,
-		}
-		applyFPD(testCase.inputFpd, bidderRequest)
-		assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description))
+		t.Run(testCase.description, func(t *testing.T) {
+			reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &testCase.inputRequest}
+			applyFPD(
+				testCase.inputFpd,
+				openrtb_ext.BidderName(testCase.inputBidderCoreName),
+				openrtb_ext.BidderName(testCase.inputBidderName),
+				testCase.inputBidderIsRequestAlias,
+				reqWrapper,
+				testCase.fpdUserEIDsExisted,
+			)
+			assert.Equal(t, &testCase.expectedRequest, reqWrapper.BidRequest)
+		})
 	}
 }
 
-func Test_parseAliasesGVLIDs(t *testing.T) {
-	type args struct {
-		orig *openrtb2.BidRequest
-	}
+func TestGetRequestAliases(t *testing.T) {
 	tests := []struct {
-		name      string
-		args      args
-		want      map[string]uint16
-		wantError bool
+		name         string
+		givenRequest openrtb_ext.RequestWrapper
+		wantAliases  map[string]string
+		wantGVLIDs   map[string]uint16
+		wantError    string
 	}{
 		{
-			"AliasGVLID Parsed Correctly",
-			args{
-				orig: &openrtb2.BidRequest{
-					Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":{"somealiascode":1}}}`),
+			name: "nil",
+			givenRequest: openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
+			wantAliases: nil,
+			wantGVLIDs:  nil,
+			wantError:   "",
+		},
+		{
+			name: "empty",
+			givenRequest: openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{}`),
 				},
 			},
-			map[string]uint16{"somealiascode": 1},
-			false,
+			wantAliases: nil,
+			wantGVLIDs:  nil,
+			wantError:   "",
 		},
 		{
-			"AliasGVLID parsing error",
-			args{
-				orig: &openrtb2.BidRequest{
-					Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids": {"somealiascode":"abc"}`),
+			name: "empty-prebid",
+			givenRequest: openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{}}`),
 				},
 			},
-			nil,
-			true,
+			wantAliases: nil,
+			wantGVLIDs:  nil,
+			wantError:   "",
 		},
 		{
-			"Invalid AliasGVLID",
-			args{
-				orig: &openrtb2.BidRequest{
-					Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}, "aliasgvlids":"abc"}`),
+			name: "aliases-and-gvlids",
+			givenRequest: openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"aliases":{"alias1":"bidder1"}, "aliasgvlids":{"alias1":1}}}`),
 				},
 			},
-			nil,
-			true,
+			wantAliases: map[string]string{"alias1": "bidder1"},
+			wantGVLIDs:  map[string]uint16{"alias1": 1},
+			wantError:   "",
 		},
 		{
-			"Missing AliasGVLID",
-			args{
-				orig: &openrtb2.BidRequest{
-					Ext: json.RawMessage(`{"prebid":{"aliases":{"somealiascode":"appnexus"}}`),
+			name: "malformed",
+			givenRequest: openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`malformed`),
 				},
 			},
-			nil,
-			false,
+			wantAliases: nil,
+			wantGVLIDs:  nil,
+			wantError:   "request.ext is invalid",
 		},
 	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, err := parseAliasesGVLIDs(tt.args.orig)
-			assert.Equal(t, tt.want, got, "parseAliasesGVLIDs() got = %v, want %v", got, tt.want)
-			if !tt.wantError && err != nil {
-				t.Errorf("parseAliasesGVLIDs() expected error got nil")
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			gotAliases, gotGVLIDs, err := getRequestAliases(&test.givenRequest)
+
+			assert.Equal(t, test.wantAliases, gotAliases, "aliases")
+			assert.Equal(t, test.wantGVLIDs, gotGVLIDs, "gvlids")
+
+			if len(test.wantError) > 0 {
+				require.Len(t, err, 1, "error-len")
+				assert.EqualError(t, err[0], test.wantError, "error")
+			} else {
+				assert.Empty(t, err, "error")
 			}
 		})
 	}
@@ -3661,7 +3888,7 @@ func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) {
 			bidderInfo:        config.BidderInfos{},
 		}
 
-		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, map[string]float64{})
+		bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{})
 		assert.Equal(t, test.wantError, len(errs) != 0, test.desc)
 		sort.Slice(bidderRequests, func(i, j int) bool {
 			return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName
@@ -3692,21 +3919,27 @@ func (gs GPPMockSection) Encode(bool) []byte {
 func TestGdprFromGPP(t *testing.T) {
 	testCases := []struct {
 		name            string
-		initialRequest  *openrtb2.BidRequest
+		initialRequest  *openrtb_ext.RequestWrapper
 		gpp             gpplib.GppContainer
-		expectedRequest *openrtb2.BidRequest
+		expectedRequest *openrtb_ext.RequestWrapper
 	}{
 		{
-			name:            "Empty", // Empty Request
-			initialRequest:  &openrtb2.BidRequest{},
-			gpp:             gpplib.GppContainer{},
-			expectedRequest: &openrtb2.BidRequest{},
+			name: "Empty", // Empty Request
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
+			gpp: gpplib.GppContainer{},
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
 		},
 		{
 			name: "GDPR_Downgrade", // GDPR from GPP, into empty
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3718,25 +3951,29 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
-					GDPR:   ptrutil.ToPtr[int8](1),
-				},
-				User: &openrtb2.User{
-					Consent: "GDPRConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+						GDPR:   ptrutil.ToPtr[int8](1),
+					},
+					User: &openrtb2.User{
+						Consent: "GDPRConsent",
+					},
 				},
 			},
 		},
 		{
 			name: "GDPR_Downgrade", // GDPR from GPP, into empty legacy, existing objects
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID:    []int8{2},
-					USPrivacy: "LegacyUSP",
-				},
-				User: &openrtb2.User{
-					ID: "1234",
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID:    []int8{2},
+						USPrivacy: "LegacyUSP",
+					},
+					User: &openrtb2.User{
+						ID: "1234",
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3748,27 +3985,31 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID:    []int8{2},
-					GDPR:      ptrutil.ToPtr[int8](1),
-					USPrivacy: "LegacyUSP",
-				},
-				User: &openrtb2.User{
-					ID:      "1234",
-					Consent: "GDPRConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID:    []int8{2},
+						GDPR:      ptrutil.ToPtr[int8](1),
+						USPrivacy: "LegacyUSP",
+					},
+					User: &openrtb2.User{
+						ID:      "1234",
+						Consent: "GDPRConsent",
+					},
 				},
 			},
 		},
 		{
 			name: "Downgrade_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR",
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
-					GDPR:   ptrutil.ToPtr[int8](1),
-				},
-				User: &openrtb2.User{
-					Consent: "LegacyConsent",
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+						GDPR:   ptrutil.ToPtr[int8](1),
+					},
+					User: &openrtb2.User{
+						Consent: "LegacyConsent",
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3780,22 +4021,26 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
-					GDPR:   ptrutil.ToPtr[int8](1),
-				},
-				User: &openrtb2.User{
-					Consent: "LegacyConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+						GDPR:   ptrutil.ToPtr[int8](1),
+					},
+					User: &openrtb2.User{
+						Consent: "LegacyConsent",
+					},
 				},
 			},
 		},
 		{
 			name: "Downgrade_Partial", // GDPR from GPP partially blocked by existing GDPR
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
-					GDPR:   ptrutil.ToPtr[int8](0),
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+						GDPR:   ptrutil.ToPtr[int8](0),
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3807,21 +4052,25 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
-					GDPR:   ptrutil.ToPtr[int8](0),
-				},
-				User: &openrtb2.User{
-					Consent: "GDPRConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+						GDPR:   ptrutil.ToPtr[int8](0),
+					},
+					User: &openrtb2.User{
+						Consent: "GDPRConsent",
+					},
 				},
 			},
 		},
 		{
 			name: "No_GDPR", // Downgrade not possible due to missing GDPR
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{6},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{6},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3833,18 +4082,22 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{6},
-					GDPR:   ptrutil.ToPtr[int8](0),
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{6},
+						GDPR:   ptrutil.ToPtr[int8](0),
+					},
 				},
 			},
 		},
 		{
 			name: "No_SID", // GDPR from GPP partially blocked by no SID
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{6},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{6},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3860,19 +4113,23 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{6},
-					GDPR:   ptrutil.ToPtr[int8](0),
-				},
-				User: &openrtb2.User{
-					Consent: "GDPRConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{6},
+						GDPR:   ptrutil.ToPtr[int8](0),
+					},
+					User: &openrtb2.User{
+						Consent: "GDPRConsent",
+					},
 				},
 			},
 		},
 		{
-			name:           "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID
-			initialRequest: &openrtb2.BidRequest{},
+			name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
 			gpp: gpplib.GppContainer{
 				SectionTypes: []constants.SectionID{2},
 				Sections: []gpplib.Section{
@@ -3882,20 +4139,24 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				User: &openrtb2.User{
-					Consent: "GDPRConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					User: &openrtb2.User{
+						Consent: "GDPRConsent",
+					},
 				},
 			},
 		},
 		{
 			name: "Downgrade_Nil_SID_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR, with nil SID",
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GDPR: ptrutil.ToPtr[int8](1),
-				},
-				User: &openrtb2.User{
-					Consent: "LegacyConsent",
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GDPR: ptrutil.ToPtr[int8](1),
+					},
+					User: &openrtb2.User{
+						Consent: "LegacyConsent",
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3907,12 +4168,14 @@ func TestGdprFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GDPR: ptrutil.ToPtr[int8](1),
-				},
-				User: &openrtb2.User{
-					Consent: "LegacyConsent",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GDPR: ptrutil.ToPtr[int8](1),
+					},
+					User: &openrtb2.User{
+						Consent: "LegacyConsent",
+					},
 				},
 			},
 		},
@@ -3929,21 +4192,27 @@ func TestGdprFromGPP(t *testing.T) {
 func TestPrivacyFromGPP(t *testing.T) {
 	testCases := []struct {
 		name            string
-		initialRequest  *openrtb2.BidRequest
+		initialRequest  *openrtb_ext.RequestWrapper
 		gpp             gpplib.GppContainer
-		expectedRequest *openrtb2.BidRequest
+		expectedRequest *openrtb_ext.RequestWrapper
 	}{
 		{
-			name:            "Empty", // Empty Request
-			initialRequest:  &openrtb2.BidRequest{},
-			gpp:             gpplib.GppContainer{},
-			expectedRequest: &openrtb2.BidRequest{},
+			name: "Empty", // Empty Request
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
+			gpp: gpplib.GppContainer{},
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{},
+			},
 		},
 		{
 			name: "Privacy_Downgrade", // US Privacy from GPP, into empty
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{6},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{6},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3955,19 +4224,23 @@ func TestPrivacyFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID:    []int8{6},
-					USPrivacy: "USPrivacy",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID:    []int8{6},
+						USPrivacy: "USPrivacy",
+					},
 				},
 			},
 		},
 		{
 			name: "Downgrade_Blocked_By_Existing", // US Privacy from GPP blocked by existing US Privacy
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID:    []int8{6},
-					USPrivacy: "LegacyPrivacy",
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID:    []int8{6},
+						USPrivacy: "LegacyPrivacy",
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -3979,18 +4252,22 @@ func TestPrivacyFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID:    []int8{6},
-					USPrivacy: "LegacyPrivacy",
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID:    []int8{6},
+						USPrivacy: "LegacyPrivacy",
+					},
 				},
 			},
 		},
 		{
 			name: "No_USPrivacy", // Downgrade not possible due to missing USPrivacy
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -4002,17 +4279,21 @@ func TestPrivacyFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+					},
 				},
 			},
 		},
 		{
 			name: "No_SID", // US Privacy from GPP partially blocked by no SID
-			initialRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
+			initialRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+					},
 				},
 			},
 			gpp: gpplib.GppContainer{
@@ -4028,9 +4309,11 @@ func TestPrivacyFromGPP(t *testing.T) {
 					},
 				},
 			},
-			expectedRequest: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{
-					GPPSID: []int8{2},
+			expectedRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Regs: &openrtb2.Regs{
+						GPPSID: []int8{2},
+					},
 				},
 			},
 		},
@@ -4520,8 +4803,10 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 		privacyConfig     config.AccountPrivacy
 		componentName     string
 		allow             bool
+		ortbVersion       string
 		expectedReqNumber int
 		expectedUser      openrtb2.User
+		expectUserScrub   bool
 		expectedDevice    openrtb2.Device
 		expectedSource    openrtb2.Source
 		expectedImpExt    json.RawMessage
@@ -4530,6 +4815,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "fetch_bids_request_with_one_bidder_allowed",
 			req:               newBidRequest(t),
 			privacyConfig:     getFetchBidsActivityConfig("appnexus", true),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser:      expectedUserDefault,
 			expectedDevice:    expectedDeviceDefault,
@@ -4548,6 +4834,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "transmit_ufpd_allowed",
 			req:               newBidRequest(t),
 			privacyConfig:     getTransmitUFPDActivityConfig("appnexus", true),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser:      expectedUserDefault,
 			expectedDevice:    expectedDeviceDefault,
@@ -4569,6 +4856,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 				Ext:      json.RawMessage(`{"test":2}`),
 				Data:     nil,
 			},
+			expectUserScrub: true,
 			expectedDevice: openrtb2.Device{
 				UA:       deviceUA,
 				Language: "EN",
@@ -4588,6 +4876,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "transmit_precise_geo_allowed",
 			req:               newBidRequest(t),
 			privacyConfig:     getTransmitPreciseGeoActivityConfig("appnexus", true),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser:      expectedUserDefault,
 			expectedDevice:    expectedDeviceDefault,
@@ -4599,6 +4888,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "transmit_precise_geo_deny",
 			req:               newBidRequest(t),
 			privacyConfig:     getTransmitPreciseGeoActivityConfig("appnexus", false),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser: openrtb2.User{
 				ID:       "our-id",
@@ -4631,6 +4921,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "transmit_tid_allowed",
 			req:               newBidRequest(t),
 			privacyConfig:     getTransmitTIDActivityConfig("appnexus", true),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser:      expectedUserDefault,
 			expectedDevice:    expectedDeviceDefault,
@@ -4641,6 +4932,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 			name:              "transmit_tid_deny",
 			req:               newBidRequest(t),
 			privacyConfig:     getTransmitTIDActivityConfig("appnexus", false),
+			ortbVersion:       "2.6",
 			expectedReqNumber: 1,
 			expectedUser:      expectedUserDefault,
 			expectedDevice:    expectedDeviceDefault,
@@ -4666,17 +4958,21 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 						AnonKeepBits: 16,
 					},
 				}},
+				TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
 			}
 
+			metricsMock := metrics.MetricsEngineMock{}
+			metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return()
+
 			bidderToSyncerKey := map[string]string{}
 			reqSplitter := &requestSplitter{
 				bidderToSyncerKey: bidderToSyncerKey,
-				me:                &metrics.MetricsEngineMock{},
+				me:                &metricsMock,
 				hostSChainNode:    nil,
-				bidderInfo:        config.BidderInfos{},
+				bidderInfo:        config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{Version: test.ortbVersion}}},
 			}
 
-			bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, map[string]float64{})
+			bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{})
 			assert.Empty(t, errs)
 			assert.Len(t, bidderRequests, test.expectedReqNumber)
 
@@ -4688,6 +4984,11 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 				if len(test.expectedImpExt) > 0 {
 					assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext))
 				}
+				if test.expectUserScrub {
+					metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
+				} else {
+					metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus)
+				}
 			}
 		})
 	}
@@ -4742,119 +5043,96 @@ func getTransmitTIDActivityConfig(componentName string, allow bool) config.Accou
 
 func TestApplyBidAdjustmentToFloor(t *testing.T) {
 	type args struct {
-		allBidderRequests    []BidderRequest
+		bidRequestWrapper    *openrtb_ext.RequestWrapper
+		bidderName           string
 		bidAdjustmentFactors map[string]float64
 	}
 	tests := []struct {
-		name                      string
-		args                      args
-		expectedAllBidderRequests []BidderRequest
+		name               string
+		args               args
+		expectedBidRequest *openrtb2.BidRequest
 	}{
 		{
-			name: " bidAdjustmentFactor is empty",
+			name: "bid_adjustment_factor_is_nil",
 			args: args{
-				allBidderRequests: []BidderRequest{
-					{
-						BidRequest: &openrtb2.BidRequest{
-							Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
-						},
-						BidderName: openrtb_ext.BidderName("appnexus"),
-					},
-				},
-				bidAdjustmentFactors: map[string]float64{},
-			},
-			expectedAllBidderRequests: []BidderRequest{
-				{
+				bidRequestWrapper: &openrtb_ext.RequestWrapper{
 					BidRequest: &openrtb2.BidRequest{
 						Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 					},
-					BidderName: openrtb_ext.BidderName("appnexus"),
 				},
+				bidderName:           "appnexus",
+				bidAdjustmentFactors: nil,
+			},
+			expectedBidRequest: &openrtb2.BidRequest{
+				Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 			},
 		},
 		{
-			name: "bidAdjustmentFactor not present for request bidder",
+			name: "bid_adjustment_factor_is_empty",
 			args: args{
-				allBidderRequests: []BidderRequest{
-					{
-						BidRequest: &openrtb2.BidRequest{
-							Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
-						},
-						BidderName: openrtb_ext.BidderName("appnexus"),
+				bidRequestWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 					},
 				},
-				bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0},
+				bidderName:           "appnexus",
+				bidAdjustmentFactors: map[string]float64{},
 			},
-			expectedAllBidderRequests: []BidderRequest{
-				{
+			expectedBidRequest: &openrtb2.BidRequest{
+				Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
+			},
+		},
+		{
+			name: "bid_adjustment_factor_not_present",
+			args: args{
+				bidRequestWrapper: &openrtb_ext.RequestWrapper{
 					BidRequest: &openrtb2.BidRequest{
 						Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 					},
-					BidderName: openrtb_ext.BidderName("appnexus"),
 				},
+				bidderName:           "appnexus",
+				bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0},
+			},
+			expectedBidRequest: &openrtb2.BidRequest{
+				Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 			},
 		},
 		{
-			name: "bidAdjustmentFactor present for request bidder",
+			name: "bid_adjustment_factor_present",
 			args: args{
-				allBidderRequests: []BidderRequest{
-					{
-						BidRequest: &openrtb2.BidRequest{
-							Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
-						},
-						BidderName: openrtb_ext.BidderName("appnexus"),
+				bidRequestWrapper: &openrtb_ext.RequestWrapper{
+					BidRequest: &openrtb2.BidRequest{
+						Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 					},
 				},
+				bidderName:           "appnexus",
 				bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75},
 			},
-			expectedAllBidderRequests: []BidderRequest{
-				{
-					BidRequest: &openrtb2.BidRequest{
-						Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}},
-					},
-					BidderName: openrtb_ext.BidderName("appnexus"),
-				},
+			expectedBidRequest: &openrtb2.BidRequest{
+				Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}},
 			},
 		},
 		{
-			name: "bidAdjustmentFactor present only for appnexus request bidder",
+			name: "bid_adjustment_factor_present_and_zero",
 			args: args{
-				allBidderRequests: []BidderRequest{
-					{
-						BidRequest: &openrtb2.BidRequest{
-							Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
-						},
-						BidderName: openrtb_ext.BidderName("appnexus"),
-					},
-					{
-						BidRequest: &openrtb2.BidRequest{
-							Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
-						},
-						BidderName: openrtb_ext.BidderName("pubmatic"),
-					},
-				},
-				bidAdjustmentFactors: map[string]float64{"appnexus": 0.75},
-			},
-			expectedAllBidderRequests: []BidderRequest{
-				{
-					BidRequest: &openrtb2.BidRequest{
-						Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}},
-					},
-					BidderName: openrtb_ext.BidderName("appnexus"),
-				},
-				{
+				bidRequestWrapper: &openrtb_ext.RequestWrapper{
 					BidRequest: &openrtb2.BidRequest{
 						Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 					},
-					BidderName: openrtb_ext.BidderName("pubmatic"),
 				},
+				bidderName:           "appnexus",
+				bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.0},
+			},
+			expectedBidRequest: &openrtb2.BidRequest{
+				Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}},
 			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			applyBidAdjustmentToFloor(tt.args.allBidderRequests, tt.args.bidAdjustmentFactors)
-			assert.Equal(t, tt.expectedAllBidderRequests, tt.args.allBidderRequests, tt.name)
+			applyBidAdjustmentToFloor(tt.args.bidRequestWrapper, tt.args.bidderName, tt.args.bidAdjustmentFactors)
+			assert.NoError(t, tt.args.bidRequestWrapper.RebuildRequest())
+			assert.Equal(t, tt.expectedBidRequest, tt.args.bidRequestWrapper.BidRequest, tt.name)
 		})
 	}
 }
@@ -5077,24 +5355,85 @@ func TestCopyExtAlternateBidderCodes(t *testing.T) {
 	}
 }
 
-func TestBuildBidResponseRequestBidderName(t *testing.T) {
-	bidderImpResponses := stored_responses.BidderImpsWithBidResponses{
-		openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)},
-		openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)},
-	}
+func TestRemoveImpsWithStoredResponses(t *testing.T) {
+	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
+	testCases := []struct {
+		description        string
+		req                *openrtb_ext.RequestWrapper
+		storedBidResponses map[string]json.RawMessage
+		expectedImps       []openrtb2.Imp
+	}{
+		{
+			description: "request with imps and stored bid response for this imp",
+			req: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{ID: "imp-id1"},
+					},
+				},
+			},
+			storedBidResponses: map[string]json.RawMessage{
+				"imp-id1": bidRespId1,
+			},
+			expectedImps: nil,
+		},
+		{
+			description: "request with imps and stored bid response for one of these imp",
+			req: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{ID: "imp-id1"},
+						{ID: "imp-id2"},
+					},
+				},
+			},
+			storedBidResponses: map[string]json.RawMessage{
+				"imp-id1": bidRespId1,
+			},
+			expectedImps: []openrtb2.Imp{
+				{
+					ID: "imp-id2",
+				},
+			},
+		},
+		{
+			description: "request with imps and stored bid response for both of these imp",
+			req: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{ID: "imp-id1"},
+						{ID: "imp-id2"},
+					},
+				},
+			},
+			storedBidResponses: map[string]json.RawMessage{
+				"imp-id1": bidRespId1,
+				"imp-id2": bidRespId1,
+			},
+			expectedImps: nil,
+		},
+		{
+			description: "request with imps and no stored bid responses",
+			req: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Imp: []openrtb2.Imp{
+						{ID: "imp-id1"},
+						{ID: "imp-id2"},
+					},
+				},
+			},
+			storedBidResponses: nil,
 
-	bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{
-		"appnexus": {"impId1": true, "impId2": false},
-		"appneXUS": {"impId3": true, "impId4": false},
+			expectedImps: []openrtb2.Imp{
+				{ID: "imp-id1"},
+				{ID: "imp-id2"},
+			},
+		},
+	}
+	for _, testCase := range testCases {
+		request := testCase.req
+		removeImpsWithStoredResponses(request, testCase.storedBidResponses)
+		assert.NoError(t, request.RebuildRequest())
+		assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description)
 	}
-	result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID)
-
-	resultAppnexus := result["appnexus"]
-	assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus"))
-	assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false})
-
-	resultAppneXUS := result["appneXUS"]
-	assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS"))
-	assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false})
-
 }
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index 1b4f6cb4680..4f7df2f3ab7 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -20,8 +20,8 @@ type Permissions interface {
 
 	// Determines whether or not to send PI information to a bidder, or mask it out.
 	//
-	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error)
+	// If the consent string was nonsensical, the no permissions are granted.
+	AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions
 }
 
 type PermissionsBuilder func(TCF2ConfigReader, RequestInfo) Permissions
diff --git a/gdpr/impl.go b/gdpr/impl.go
index fd3ad2b2dd9..d364883b91a 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -56,33 +56,36 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 }
 
 // AuctionActivitiesAllowed determines whether auction activities are permitted for a given bidder
-func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
+func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions {
 	if _, ok := p.nonStandardPublishers[p.publisherID]; ok {
-		return AllowAll, nil
+		return AllowAll
 	}
+
 	if p.gdprSignal != SignalYes {
-		return AllowAll, nil
+		return AllowAll
 	}
+
 	if p.consent == "" {
-		return p.defaultPermissions(), nil
+		return p.defaultPermissions()
 	}
+
 	pc, err := parseConsent(p.consent)
 	if err != nil {
-		return p.defaultPermissions(), err
+		return p.defaultPermissions()
 	}
+
 	vendorID, _ := p.resolveVendorID(bidderCoreName, bidder)
 	vendor, err := p.getVendor(ctx, vendorID, *pc)
 	if err != nil {
-		return p.defaultPermissions(), err
+		return p.defaultPermissions()
 	}
-	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
-
-	permissions = AuctionPermissions{}
-	permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo)
-	permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor)
-	permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo)
 
-	return permissions, nil
+	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
+	return AuctionPermissions{
+		AllowBidRequest: p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo),
+		PassGeo:         p.allowGeo(bidderCoreName, pc.consentMeta, vendor),
+		PassID:          p.allowID(bidderCoreName, pc.consentMeta, vendorInfo),
+	}
 }
 
 // defaultPermissions returns a permissions object that denies passing user IDs while
@@ -222,6 +225,6 @@ func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context) (bool, error) {
 func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) {
 	return true, nil
 }
-func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
-	return AllowAll, nil
+func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions {
+	return AllowAll
 }
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index fc3d69d9c57..928d4fe8e82 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -335,9 +335,8 @@ func TestAllowActivities(t *testing.T) {
 		perms.gdprSignal = tt.gdpr
 		perms.publisherID = tt.publisherID
 
-		permissions, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName)
+		permissions := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName)
 
-		assert.Nil(t, err, tt.description)
 		assert.Equal(t, tt.passID, permissions.PassID, tt.description)
 	}
 }
@@ -437,8 +436,7 @@ func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) {
 				purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig),
 			}
 
-			permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID)
-			assert.NoError(t, err)
+			permissions := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID)
 			assert.Equal(t, tt.allowBidRequest, permissions.AllowBidRequest)
 			assert.Equal(t, tt.passID, permissions.PassID)
 		})
@@ -658,8 +656,7 @@ func TestAllowActivitiesGeoAndID(t *testing.T) {
 		perms.consent = td.consent
 		perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig)
 
-		permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
-		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+		permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
 		assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description)
 		assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description)
@@ -695,8 +692,7 @@ func TestAllowActivitiesWhitelist(t *testing.T) {
 	}
 
 	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
-	permissions, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus)
-	assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed")
+	permissions := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus)
 	assert.EqualValuesf(t, true, permissions.PassGeo, "PassGeo failure")
 	assert.EqualValuesf(t, true, permissions.PassID, "PassID failure")
 }
@@ -767,8 +763,7 @@ func TestAllowActivitiesPubRestrict(t *testing.T) {
 		perms.aliasGVLIDs = td.aliasGVLIDs
 		perms.consent = td.consent
 
-		permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
-		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+		permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
 		assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description)
 		assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description)
 	}
@@ -1101,8 +1096,7 @@ func TestAllowActivitiesBidRequests(t *testing.T) {
 		perms.cfg = &tcf2AggConfig
 		perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig)
 
-		permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
-		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+		permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
 		assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description)
 		assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description)
@@ -1195,8 +1189,7 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 		perms.cfg = &tcf2AggConfig
 		perms.purposeEnforcerBuilder = NewPurposeEnforcerBuilder(&tcf2AggConfig)
 
-		permissions, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
-		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
+		permissions := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder)
 		assert.EqualValuesf(t, td.allowBidRequest, permissions.AllowBidRequest, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, permissions.PassGeo, "PassGeo failure on %s", td.description)
 		assert.EqualValuesf(t, td.passID, permissions.PassID, "PassID failure on %s", td.description)
diff --git a/go.mod b/go.mod
index 94e2a2a4e3c..090a39357cf 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
 	github.com/prometheus/client_golang v1.12.1
 	github.com/prometheus/client_model v0.2.0
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
-	github.com/rs/cors v1.8.2
+	github.com/rs/cors v1.11.0
 	github.com/spf13/viper v1.12.0
 	github.com/stretchr/testify v1.8.1
 	github.com/vrischmann/go-metrics-influxdb v0.1.1
@@ -41,6 +41,7 @@ require (
 )
 
 require (
+	github.com/51Degrees/device-detection-go/v4 v4.4.35 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -66,6 +67,10 @@ require (
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/subosito/gotenv v1.3.0 // indirect
+	github.com/tidwall/gjson v1.17.1 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.0 // indirect
+	github.com/tidwall/sjson v1.2.5 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
diff --git a/go.sum b/go.sum
index bed6194599a..80b58cd7c33 100644
--- a/go.sum
+++ b/go.sum
@@ -49,6 +49,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/51Degrees/device-detection-go/v4 v4.4.35 h1:qhP2tzoXhGE1aYY3NftMJ+ccxz0+2kM8aF4SH7fTyuA=
+github.com/51Degrees/device-detection-go/v4 v4.4.35/go.mod h1:dbdG1fySqdY+a5pUnZ0/G0eD03G6H3Vh8kRC+1f9qSc=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
@@ -135,6 +137,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
+github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
@@ -315,9 +318,11 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
 github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
@@ -435,8 +440,9 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqn
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
-github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U=
-github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
+github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -482,6 +488,15 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
 github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
+github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/vrischmann/go-metrics-influxdb v0.1.1 h1:xneKFRjsS4BiVYvAKaM/rOlXYd1pGHksnES0ECCJLgo=
 github.com/vrischmann/go-metrics-influxdb v0.1.1/go.mod h1:q7YC8bFETCYopXRMtUvQQdLaoVhpsEwvQS2zZEYCqg8=
diff --git a/injector/injector_test.go b/injector/injector_test.go
new file mode 100644
index 00000000000..1a6385c48e9
--- /dev/null
+++ b/injector/injector_test.go
@@ -0,0 +1,455 @@
+package injector
+
+import (
+	"errors"
+	"strings"
+	"testing"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+	"github.com/prebid/prebid-server/v2/exchange/entities"
+	"github.com/prebid/prebid-server/v2/macros"
+	"github.com/prebid/prebid-server/v2/openrtb_ext"
+	"github.com/prebid/prebid-server/v2/util/ptrutil"
+	"github.com/stretchr/testify/assert"
+)
+
+var 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: ptrutil.ToPtr(int8(1)),
+		},
+		User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)},
+		Ext:  []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`),
+	},
+}
+
+func TestInjectTracker(t *testing.T) {
+	b := macros.NewProvider(reqWrapper)
+	b.PopulateBidMacros(&entities.PbsOrtbBid{
+		Bid: &openrtb2.Bid{
+			ID: "bid123",
+		},
+	}, "testSeat")
+	ti := NewTrackerInjector(
+		macros.NewStringIndexBasedReplacer(),
+		b,
+		VASTEvents{
+			Errors:                 []string{"http://errortracker.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##"},
+			Impressions:            []string{"http://impressiontracker.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##"},
+			VideoClicks:            []string{"http://videoclicktracker.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##"},
+			NonLinearClickTracking: []string{"http://nonlinearclicktracker.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##"},
+			CompanionClickThrough:  []string{"http://companionclicktracker.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##"},
+			TrackingEvents:         map[string][]string{"firstQuartile": {"http://eventracker1.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##"}},
+		},
+	)
+	type args struct {
+		vastXML string
+		NURL    string
+	}
+	tests := []struct {
+		name      string
+		args      args
+		want      string
+		wantError error
+	}{
+		{
+			name: "Empty vastXML and NURL present",
+			args: args{
+				vastXML: "",
+				NURL:    "www.nurl.com",
+			},
+			want:      `<VAST version="3.0"><Ad><Wrapper><AdSystem>prebid.org wrapper</AdSystem><VASTAdTagURI><![CDATA[www.nurl.com]]></VASTAdTagURI><Creatives></Creatives></Wrapper></Ad></VAST>`,
+			wantError: nil,
+		},
+		{
+			name: "Empty vastXML and empty NURL",
+			args: args{
+				vastXML: "",
+				NURL:    "",
+			},
+			want:      "",
+			wantError: errors.New("invalid Vast XML"),
+		},
+		{
+			name: "No Inline/Wrapper tag present",
+			args: args{
+				vastXML: `<VAST version="4.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" sequence="1" conditionalAd="false"></Ad></VAST>`,
+				NURL:    "",
+			},
+			want:      `<VAST version="4.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" sequence="1" conditionalAd="false"></Ad></VAST>`,
+			wantError: errors.New("invalid VastXML, inline/wrapper tag not found"),
+		},
+		{
+			name: "Invalid Vast XML, parsing error",
+			args: args{
+				vastXML: `<VAST version="4.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" sequence="1" conditionalAd="false"><InLine><AdSystem version="4.0">iabtechlab</AdSystem><Error>http://example.com/error<Impression id="Impression-ID">http://example.com/track/impression</Impression><Pricing model="cpm" currency="USD"><![CDATA[ 25.00 ]]></Pricing><AdTitle>Inline Simple Ad</AdTitle><AdVerifications></AdVerifications><Advertiser>IAB Sample Company</Advertiser><Category authority="http://www.iabtechlab.com/categoryauthority">AD CONTENT description category</Category><Creatives><Creative id="5480" sequence="1" adId="2447226"><UniversalAdId idRegistry="Ad-ID" idValue="8465">8465</UniversalAdId><Linear><Duration>00:00:16</Duration><MediaFiles><MediaFile id="5241" delivery="progressive" type="video/mp4" bitrate="2000" width="1280" height="720" minBitrate="1500" maxBitrate="2500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id="5244" delivery="progressive" type="video/mp4" bitrate="1000" width="854" height="480" minBitrate="700" maxBitrate="1500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><MediaFile id="5246" delivery="progressive" type="video/mp4" bitrate="600" width="640" height="360" minBitrate="500" maxBitrate="700" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>`,
+				NURL:    "",
+			},
+			want:      ``,
+			wantError: errors.New("XML processing error: xml: end tag </InLine> does not match start tag <Error>"),
+		},
+		{
+			name: "Inline Linear vastXML, no existing event tracker",
+			args: args{
+				vastXML: `<VAST version="4.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" sequence="1" conditionalAd="false"><InLine><AdSystem version="4.0">iabtechlab</AdSystem><Error>http://example.com/error</Error><Impression id="Impression-ID">http://example.com/track/impression</Impression><Pricing model="cpm" currency="USD"><![CDATA[ 25.00 ]]></Pricing><AdTitle>Inline Simple Ad</AdTitle><AdVerifications></AdVerifications><Advertiser>IAB Sample Company</Advertiser><Category authority="http://www.iabtechlab.com/categoryauthority">AD CONTENT description category</Category><Creatives><Creative id="5480" sequence="1" adId="2447226"><UniversalAdId idRegistry="Ad-ID" idValue="8465">8465</UniversalAdId><Linear><Duration>00:00:16</Duration><MediaFiles><MediaFile id="5241" delivery="progressive" type="video/mp4" bitrate="2000" width="1280" height="720" minBitrate="1500" maxBitrate="2500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id="5244" delivery="progressive" type="video/mp4" bitrate="1000" width="854" height="480" minBitrate="700" maxBitrate="1500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><MediaFile id="5246" delivery="progressive" type="video/mp4" bitrate="600" width="640" height="360" minBitrate="500" maxBitrate="700" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>`,
+				NURL:    "",
+			},
+			want: `<VAST version="4.0" xmlns:_xmlns="xmlns" _xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" sequence="1" conditionalAd="false"><InLine><AdSystem version="4.0"><![CDATA[iabtechlab]]></AdSystem><Error><![CDATA[http://example.com/error]]></Error><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error><Impression id="Impression-ID"><![CDATA[http://example.com/track/impression]]></Impression><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Pricing model="cpm" currency="USD"><![CDATA[25.00]]></Pricing><AdTitle><![CDATA[Inline Simple Ad]]></AdTitle><AdVerifications></AdVerifications><Advertiser><![CDATA[IAB Sample Company]]></Advertiser><Category authority="http://www.iabtechlab.com/categoryauthority"><![CDATA[AD CONTENT description category]]></Category><Creatives><Creative id="5480" sequence="1" adId="2447226"><UniversalAdId idRegistry="Ad-ID" idValue="8465"><![CDATA[8465]]></UniversalAdId><Linear><Duration><![CDATA[00:00:16]]></Duration><MediaFiles><MediaFile id="5241" delivery="progressive" type="video/mp4" bitrate="2000" width="1280" height="720" minBitrate="1500" maxBitrate="2500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro.mp4]]></MediaFile><MediaFile id="5244" delivery="progressive" type="video/mp4" bitrate="1000" width="854" height="480" minBitrate="700" maxBitrate="1500" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-mid-resolution.mp4]]></MediaFile><MediaFile id="5246" delivery="progressive" type="video/mp4" bitrate="600" width="640" height="360" minBitrate="500" maxBitrate="700" scalable="1" maintainAspectRatio="1" codec="H.264"><![CDATA[https://iab-publicfiles.s3.amazonaws.com/vast/VAST-4.0-Short-Intro-low-resolution.mp4]]></MediaFile></MediaFiles><VideoClicks><ClickTracking><![CDATA[http://videoclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></ClickTracking></VideoClicks><TrackingEvents><Tracking event="firstQuartile"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking></TrackingEvents></Linear></Creative></Creatives></InLine></Ad></VAST>`,
+		},
+		{
+			name: "Non Linear vastXML, no existing event tracker",
+			args: args{
+				NURL:    "",
+				vastXML: `<VAST version="4.0" xmlns="http://www.iab.com/VAST"><Ad id="20005" sequence="1" conditionalAd="false"><InLine><AdSystem version="4.0">iabtechlab</AdSystem><Extensions><Extension type="iab-Count"><total_available><![CDATA[ 2 ]]></total_available></Extension></Extensions><Pricing model="cpm" currency="USD"><![CDATA[ 25.00 ]]></Pricing><AdTitle><![CDATA[VAST 4.0 Pilot - Scenario 5]]></AdTitle><Creatives><Creative id="5480" sequence="1" adId="2447226"><UniversalAdId idRegistry="Ad-ID" idValue="8465">8465</UniversalAdId><NonLinearAds><NonLinear><StaticResource creativeType="image/png"><![CDATA[http://mms.businesswire.com/media/20150623005446/en/473787/21/iab_tech_lab.jpg]]></StaticResource></NonLinear></NonLinearAds></Creative></Creatives><Description><![CDATA[VAST 4.0 sample tag for Non Linear ad (i.e Overlay ad). Change the StaticResources to have a tag with your own content. Change NonLinear tag's parameters accordingly to view desired results.]]></Description></InLine></Ad></VAST>`,
+			},
+			want: `<VAST version="4.0" xmlns="http://www.iab.com/VAST"><Ad id="20005" sequence="1" conditionalAd="false"><InLine><AdSystem version="4.0"><![CDATA[iabtechlab]]></AdSystem><Extensions><Extension type="iab-Count"><total_available><![CDATA[2]]></total_available></Extension></Extensions><Pricing model="cpm" currency="USD"><![CDATA[25.00]]></Pricing><AdTitle><![CDATA[VAST 4.0 Pilot - Scenario 5]]></AdTitle><Creatives><Creative id="5480" sequence="1" adId="2447226"><UniversalAdId idRegistry="Ad-ID" idValue="8465"><![CDATA[8465]]></UniversalAdId><NonLinearAds><NonLinear><StaticResource creativeType="image/png"><![CDATA[http://mms.businesswire.com/media/20150623005446/en/473787/21/iab_tech_lab.jpg]]></StaticResource><NonLinearClickTracking><![CDATA[http://nonlinearclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></NonLinearClickTracking></NonLinear><TrackingEvents><Tracking event="firstQuartile"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking></TrackingEvents></NonLinearAds></Creative></Creatives><Description><![CDATA[VAST 4.0 sample tag for Non Linear ad (i.e Overlay ad). Change the StaticResources to have a tag with your own content. Change NonLinear tag's parameters accordingly to view desired results.]]></Description><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error></InLine></Ad></VAST>`,
+		},
+		{
+			name: "Wrapper Liner vastXML",
+			args: args{
+				NURL:    "",
+				vastXML: `<VAST version="4.0" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1" conditionalAd="false"><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0">iabtechlab</AdSystem><Error>http://example.com/error</Error><Impression id="Impression-ID">http://example.com/track/impression</Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds><Companion id="1232" width="100" height="150" assetWidth="250" assetHeight="200" expandedWidth="350" expandedHeight="250" apiFramework="VPAID" adSlotID="3214" pxratio="1400"><StaticResource creativeType="image/png"><![CDATA[https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png]]></StaticResource><CompanionClickThrough><![CDATA[https://iabtechlab.com]]></CompanionClickThrough></Companion></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.0%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+			},
+			want: `<VAST version="4.0" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1" conditionalAd="false"><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0"><![CDATA[iabtechlab]]></AdSystem><Error><![CDATA[http://example.com/error]]></Error><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error><Impression id="Impression-ID"><![CDATA[http://example.com/track/impression]]></Impression><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds><Companion id="1232" width="100" height="150" assetWidth="250" assetHeight="200" expandedWidth="350" expandedHeight="250" apiFramework="VPAID" adSlotID="3214" pxratio="1400"><StaticResource creativeType="image/png"><![CDATA[https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png]]></StaticResource><CompanionClickThrough><![CDATA[https://iabtechlab.com]]></CompanionClickThrough><CompanionClickThrough><![CDATA[http://companionclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></CompanionClickThrough></Companion></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.0%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+		},
+		{
+			name: "Wapper companion vastXML",
+			args: args{
+				NURL:    "",
+				vastXML: `<VAST version="4.2" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1" ><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0">iabtechlab</AdSystem><Error><![CDATA[https://example.com/error]]></Error><Impression id="Impression-ID"><![CDATA[https://example.com/track/impression]]></Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds><Companion id="1232" width="100" height="150" assetWidth="250" assetHeight="200" expandedWidth="350" expandedHeight="250" apiFramework="SIMID" adSlotId="3214" pxratio="1400"><StaticResource creativeType="image/png"><![CDATA[https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png]]></StaticResource><CompanionClickThrough><![CDATA[https://iabtechlab.com]]></CompanionClickThrough></Companion></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.2%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+			},
+			want: `<VAST version="4.2" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1"><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0"><![CDATA[iabtechlab]]></AdSystem><Error><![CDATA[https://example.com/error]]></Error><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error><Impression id="Impression-ID"><![CDATA[https://example.com/track/impression]]></Impression><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds><Companion id="1232" width="100" height="150" assetWidth="250" assetHeight="200" expandedWidth="350" expandedHeight="250" apiFramework="SIMID" adSlotId="3214" pxratio="1400"><StaticResource creativeType="image/png"><![CDATA[https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png]]></StaticResource><CompanionClickThrough><![CDATA[https://iabtechlab.com]]></CompanionClickThrough><CompanionClickThrough><![CDATA[http://companionclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></CompanionClickThrough></Companion></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.2%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+		},
+		{
+			name: "Wapper no companion vastXML",
+			args: args{
+				NURL:    "",
+				vastXML: `<VAST version="4.2" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1" ><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0">iabtechlab</AdSystem><Error><![CDATA[https://example.com/error]]></Error><Impression id="Impression-ID"><![CDATA[https://example.com/track/impression]]></Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.2%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+			},
+			want: `<VAST version="4.2" xmlns="http://www.iab.com/VAST"><Ad id="20011" sequence="1"><Wrapper followAdditionalWrappers="0" allowMultipleAds="1" fallbackOnNoAd="0"><AdSystem version="4.0"><![CDATA[iabtechlab]]></AdSystem><Error><![CDATA[https://example.com/error]]></Error><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error><Impression id="Impression-ID"><![CDATA[https://example.com/track/impression]]></Impression><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Creatives><Creative id="5480" sequence="1" adId="2447226"><CompanionAds><Companion><CompanionClickThrough><![CDATA[http://companionclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></CompanionClickThrough></Companion></CompanionAds></Creative></Creatives><VASTAdTagURI><![CDATA[https://raw.githubusercontent.com/InteractiveAdvertisingBureau/VAST_Samples/master/VAST%204.2%20Samples/Inline_Companion_Tag-test.xml]]></VASTAdTagURI></Wrapper></Ad></VAST>`,
+		},
+		{
+			name: "Inline Non Linear empty",
+			args: args{
+				NURL:    "",
+				vastXML: `<VAST version="4.2" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001" ><InLine><AdSystem version="1">iabtechlab</AdSystem><Pricing model="cpm" currency="USD"><![CDATA[ 25.00 ]]></Pricing><AdServingId>a532d16d-4d7f-4440-bd29-2ec0e693fc80</AdServingId><AdTitle>iabtechlab video ad</AdTitle><Creatives><Creative id="5480" sequence="1" adId="2447226"><NonLinearAds></NonLinearAds></Creative></Creatives></InLine></Ad></VAST>`,
+			},
+			want: `<VAST version="4.2" xmlns:_xmlns="xmlns" _xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.iab.com/VAST"><Ad id="20001"><InLine><AdSystem version="1"><![CDATA[iabtechlab]]></AdSystem><Pricing model="cpm" currency="USD"><![CDATA[25.00]]></Pricing><AdServingId><![CDATA[a532d16d-4d7f-4440-bd29-2ec0e693fc80]]></AdServingId><AdTitle><![CDATA[iabtechlab video ad]]></AdTitle><Creatives><Creative id="5480" sequence="1" adId="2447226"><NonLinearAds><TrackingEvents><Tracking event="firstQuartile"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking></TrackingEvents></NonLinearAds></Creative></Creatives><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error></InLine></Ad></VAST>`,
+		},
+		{
+			name: "Wrapper linear and non linear",
+			args: args{
+				NURL:    "",
+				vastXML: `<?xml version="1.0" encoding="UTF-8"?><VAST version="3.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../vast/vast3_draft.xsd"><Ad id="1" sequence="1"><Wrapper><AdSystem version="1.0">Test Ad Server</AdSystem><VASTAdTagURI><![CDATA[http://localhost/test/resources/vast/inlines/test_vast_inline_with-linear-ad.xml]]></VASTAdTagURI><Creatives><Creative><NonLinearAds></NonLinearAds></Creative><Creative><Linear><TrackingEvents><Tracking event="start"><![CDATA[http://example.com/start?d=[CACHEBUSTER]]]></Tracking><Tracking event="start"><![CDATA[http://example.com/start2?d=[CACHEBUSTER]]]></Tracking><Tracking event="firstQuartile"><![CDATA[http://example.com/q2?d=[CACHEBUSTER]]]></Tracking><Tracking event="midpoint"><![CDATA[http://example.com/q3?d=[CACHEBUSTER]]]></Tracking><Tracking event="thirdQuartile"><![CDATA[http://example.com/q4?d=[CACHEBUSTER]]]></Tracking><Tracking event="complete"><![CDATA[http://example.com/complete?d=[CACHEBUSTER]]]></Tracking></TrackingEvents><VideoClicks><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click]]></ClickTracking><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click2]]></ClickTracking><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click3]]></ClickTracking><ClickTracking id="post_video_click"><![CDATA[http://example.com/linear-post-video-click]]></ClickTracking><ClickTracking id="post_video_click"><![CDATA[http://example.com/linear-post-video-click2]]></ClickTracking></VideoClicks></Linear></Creative></Creatives></Wrapper></Ad></VAST>`,
+			},
+			want: `<?xml version="1.0" encoding="UTF-8"?><VAST version="3.0" xmlns:_xmlns="xmlns" _xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="xsi" xsi:noNamespaceSchemaLocation="../../vast/vast3_draft.xsd"><Ad id="1" sequence="1"><Wrapper><AdSystem version="1.0"><![CDATA[Test Ad Server]]></AdSystem><VASTAdTagURI><![CDATA[http://localhost/test/resources/vast/inlines/test_vast_inline_with-linear-ad.xml]]></VASTAdTagURI><Creatives><Creative><NonLinearAds><TrackingEvents><Tracking event="firstQuartile"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking></TrackingEvents><NonLinear><NonLinearClickTracking><![CDATA[http://nonlinearclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></NonLinearClickTracking></NonLinear></NonLinearAds></Creative><Creative><Linear><TrackingEvents><Tracking event="firstQuartile"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking><Tracking event="start"><![CDATA[http://example.com/start?d=[CACHEBUSTER]]]></Tracking><Tracking event="start"><![CDATA[http://example.com/start2?d=[CACHEBUSTER]]]></Tracking><Tracking event="firstQuartile"><![CDATA[http://example.com/q2?d=[CACHEBUSTER]]]></Tracking><Tracking event="midpoint"><![CDATA[http://example.com/q3?d=[CACHEBUSTER]]]></Tracking><Tracking event="thirdQuartile"><![CDATA[http://example.com/q4?d=[CACHEBUSTER]]]></Tracking><Tracking event="complete"><![CDATA[http://example.com/complete?d=[CACHEBUSTER]]]></Tracking></TrackingEvents><VideoClicks><ClickTracking><![CDATA[http://videoclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></ClickTracking><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click]]></ClickTracking><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click2]]></ClickTracking><ClickTracking id="video_click"><![CDATA[http://example.com/linear-video-click3]]></ClickTracking><ClickTracking id="post_video_click"><![CDATA[http://example.com/linear-post-video-click]]></ClickTracking><ClickTracking id="post_video_click"><![CDATA[http://example.com/linear-post-video-click2]]></ClickTracking></VideoClicks></Linear></Creative></Creatives><Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression><Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error></Wrapper></Ad></VAST>`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := ti.InjectTracker(tt.args.vastXML, tt.args.NURL)
+			assert.Equal(t, tt.want, got, tt.name)
+			if tt.wantError != nil {
+				assert.EqualError(t, err, tt.wantError.Error())
+			}
+		})
+	}
+}
+
+func TestAddClickTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "With Parent Tag",
+			addParentTag: true,
+			expected:     "<VideoClicks><ClickTracking><![CDATA[http://videoclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></ClickTracking></VideoClicks>",
+		},
+		{
+			name:         "Without Parent Tag",
+			addParentTag: false,
+			expected:     "<ClickTracking><![CDATA[http://videoclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></ClickTracking>",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					VideoClicks: []string{"http://videoclicktracker.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##"},
+				},
+			)
+			ti.addClickTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestAddImpressionTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "Add impression tag",
+			addParentTag: true,
+			expected:     "<Impression><![CDATA[http://impressiontracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Impression>",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					Impressions: []string{"http://impressiontracker.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##"},
+				},
+			)
+			ti.addImpressionTrackingEvent(&outputXML)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestAddErrorTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "Add impression tag",
+			addParentTag: true,
+			expected:     "<Error><![CDATA[http://errortracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Error>",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					Errors: []string{"http://errortracker.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##"},
+				},
+			)
+			ti.addErrorTrackingEvent(&outputXML)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestAddNonLinearClickTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "With Parent Tag",
+			addParentTag: true,
+			expected:     "<NonLinear><NonLinearClickTracking><![CDATA[http://nonlinearclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></NonLinearClickTracking></NonLinear>",
+		},
+		{
+			name:         "Without Parent Tag",
+			addParentTag: false,
+			expected:     "<NonLinearClickTracking><![CDATA[http://nonlinearclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></NonLinearClickTracking>",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					NonLinearClickTracking: []string{"http://nonlinearclicktracker.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##"},
+				},
+			)
+			ti.addNonLinearClickTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestAddCompanionClickThroughEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "With Parent Tag",
+			addParentTag: true,
+			expected:     "<Companion><CompanionClickThrough><![CDATA[http://companionclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></CompanionClickThrough></Companion>",
+		},
+		{
+			name:         "Without Parent Tag",
+			addParentTag: false,
+			expected:     "<CompanionClickThrough><![CDATA[http://companionclicktracker.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></CompanionClickThrough>",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					CompanionClickThrough: []string{"http://companionclicktracker.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##"},
+				},
+			)
+			ti.addCompanionClickThroughEvent(&outputXML, "testCreativeId", tt.addParentTag)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestAddTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name         string
+		addParentTag bool
+		expected     string
+	}{
+		{
+			name:         "With Parent Tag",
+			addParentTag: true,
+			expected:     "<TrackingEvents><Tracking event=\"firstQuartile\"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking></TrackingEvents>",
+		},
+		{
+			name:         "Without Parent Tag",
+			addParentTag: false,
+			expected:     "<Tracking event=\"firstQuartile\"><![CDATA[http://eventracker1.com?macro1=bid123&macro2=testbundle&macro3=testbundle&macro4=publishertestdomain&macro5=pageurltest&macro6=testpublisherID&macro6=1&macro7=1&macro8=1&macro9=&macro10=]]></Tracking>",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					TrackingEvents: map[string][]string{"firstQuartile": {"http://eventracker1.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##"}},
+				},
+			)
+			ti.addTrackingEvent(&outputXML, "testCreativeId", tt.addParentTag)
+			assert.Equal(t, tt.expected, outputXML.String(), tt.name)
+		})
+	}
+}
+
+func TestWriteTrackingEvent(t *testing.T) {
+	tests := []struct {
+		name        string
+		urls        []string
+		startTag    string
+		endTag      string
+		creativeId  string
+		eventType   string
+		vastEvent   string
+		expectedXML string
+	}{
+		{
+			name:        "Single URL",
+			urls:        []string{"http://tracker.com"},
+			startTag:    "<Tracking>",
+			endTag:      "</Tracking>",
+			creativeId:  "123",
+			eventType:   "start",
+			vastEvent:   "tracking",
+			expectedXML: "<Tracking>http://tracker.com</Tracking>",
+		},
+		{
+			name:        "Multiple URL",
+			urls:        []string{"http://tracker1.com", "http://tracker2.com"},
+			startTag:    "<Tracking>",
+			endTag:      "</Tracking>",
+			creativeId:  "123",
+			eventType:   "start",
+			vastEvent:   "tracking",
+			expectedXML: "<Tracking>http://tracker1.com</Tracking><Tracking>http://tracker2.com</Tracking>",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var outputXML strings.Builder
+			b := macros.NewProvider(reqWrapper)
+			b.PopulateBidMacros(&entities.PbsOrtbBid{
+				Bid: &openrtb2.Bid{
+					ID: "bid123",
+				},
+			}, "testSeat")
+			ti := NewTrackerInjector(
+				macros.NewStringIndexBasedReplacer(),
+				b,
+				VASTEvents{
+					TrackingEvents: map[string][]string{"firstQuartile": {"http://eventracker1.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##"}},
+				},
+			)
+			ti.writeTrackingEvent(tt.urls, &outputXML, tt.startTag, tt.endTag, tt.creativeId, tt.eventType, tt.vastEvent)
+			assert.Equal(t, tt.expectedXML, outputXML.String(), tt.name)
+		})
+	}
+}
diff --git a/macros/macros.go b/macros/macros.go
index bde843c3cbb..2b0e29d6238 100644
--- a/macros/macros.go
+++ b/macros/macros.go
@@ -17,6 +17,10 @@ type EndpointTemplateParams struct {
 	GvlID       string
 	PageID      string
 	SupplyId    string
+	SspId       string
+	SspID       string
+	SeatID      string
+	TokenID     string
 }
 
 // UserSyncPrivacy specifies privacy policy macros, represented as strings, for user sync urls.
diff --git a/macros/provider.go b/macros/provider.go
index 3cae540e22a..29b8836bcd2 100644
--- a/macros/provider.go
+++ b/macros/provider.go
@@ -104,9 +104,8 @@ func (b *macroProvider) populateRequestMacros(reqWrapper *openrtb_ext.RequestWra
 		}
 	}
 
-	userExt, err := reqWrapper.GetUserExt()
-	if err == nil && userExt != nil && userExt.GetConsent() != nil {
-		b.macros[MacroKeyConsent] = *userExt.GetConsent()
+	if reqWrapper.User != nil && len(reqWrapper.User.Consent) > 0 {
+		b.macros[MacroKeyConsent] = reqWrapper.User.Consent
 	}
 	if reqWrapper.Device != nil && reqWrapper.Device.Lmt != nil {
 		b.macros[MacroKeyLmtTracking] = strconv.Itoa(int(*reqWrapper.Device.Lmt))
diff --git a/macros/provider_test.go b/macros/provider_test.go
index b3f5c9a88a9..a560ee80bc0 100644
--- a/macros/provider_test.go
+++ b/macros/provider_test.go
@@ -132,7 +132,7 @@ func TestPopulateRequestMacros(t *testing.T) {
 			args: args{
 				reqWrapper: &openrtb_ext.RequestWrapper{
 					BidRequest: &openrtb2.BidRequest{
-						User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)},
+						User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)},
 						Ext:  []byte(`{"prebid":{"integration":"testIntegration"}}`),
 					},
 				},
@@ -189,7 +189,7 @@ func TestPopulateRequestMacros(t *testing.T) {
 						Device: &openrtb2.Device{
 							Lmt: &lmt,
 						},
-						User: &openrtb2.User{Ext: []byte(`{"consent":"1" }`)},
+						User: &openrtb2.User{Consent: "1", Ext: []byte(`{"consent":"2" }`)},
 						Ext:  []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1"}}}`),
 					},
 				},
diff --git a/macros/string_index_based_replacer_test.go b/macros/string_index_based_replacer_test.go
index eb81a1520e9..d8d0971de31 100644
--- a/macros/string_index_based_replacer_test.go
+++ b/macros/string_index_based_replacer_test.go
@@ -123,7 +123,7 @@ var req *openrtb_ext.RequestWrapper = &openrtb_ext.RequestWrapper{
 		Device: &openrtb2.Device{
 			Lmt: &lmt,
 		},
-		User: &openrtb2.User{Ext: []byte(`{"consent":"yes" }`)},
+		User: &openrtb2.User{Consent: "yes", Ext: []byte(`{"consent":"no" }`)},
 		Ext:  []byte(`{"prebid":{"channel": {"name":"test1"},"macros":{"CUSTOMMACR1":"value1","CUSTOMMACR2":"value2","CUSTOMMACR3":"value3"}}}`),
 	},
 }
diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go
index 5badc348e61..f6a4f192224 100644
--- a/metrics/config/metrics_test.go
+++ b/metrics/config/metrics_test.go
@@ -39,17 +39,14 @@ func TestGoMetricsEngine(t *testing.T) {
 	}
 }
 
-// Test the multiengine
 func TestMultiMetricsEngine(t *testing.T) {
 	cfg := mainConfig.Configuration{}
 	cfg.Metrics.Influxdb.Host = "localhost"
 	adapterList := openrtb_ext.CoreBidderNames()
 	goEngine := metrics.NewMetrics(gometrics.NewPrefixedRegistry("prebidserver."), adapterList, mainConfig.DisabledMetrics{}, nil, modulesStages)
-	engineList := make(MultiMetricsEngine, 2)
-	engineList[0] = goEngine
-	engineList[1] = &NilMetricsEngine{}
-	var metricsEngine metrics.MetricsEngine
-	metricsEngine = &engineList
+	metricsEngine := make(MultiMetricsEngine, 2)
+	metricsEngine[0] = goEngine
+	metricsEngine[1] = &NilMetricsEngine{}
 	labels := metrics.Labels{
 		Source:        metrics.DemandWeb,
 		RType:         metrics.ReqTypeORTB2Web,
@@ -109,23 +106,23 @@ func TestMultiMetricsEngine(t *testing.T) {
 		metricsEngine.RecordModuleExecutionError(module)
 		metricsEngine.RecordModuleTimeout(module)
 	}
-	labelsBlacklist := []metrics.Labels{
+	labelsBlocked := []metrics.Labels{
 		{
 			Source:        metrics.DemandWeb,
 			RType:         metrics.ReqTypeAMP,
 			PubID:         "test2",
 			CookieFlag:    metrics.CookieFlagYes,
-			RequestStatus: metrics.RequestStatusBlacklisted,
+			RequestStatus: metrics.RequestStatusBlockedApp,
 		},
 		{
 			Source:        metrics.DemandWeb,
 			RType:         metrics.ReqTypeVideo,
 			PubID:         "test2",
 			CookieFlag:    metrics.CookieFlagYes,
-			RequestStatus: metrics.RequestStatusBlacklisted,
+			RequestStatus: metrics.RequestStatusBlockedApp,
 		},
 	}
-	for _, label := range labelsBlacklist {
+	for _, label := range labelsBlocked {
 		metricsEngine.RecordRequest(label)
 	}
 	impTypeLabels.BannerImps = false
@@ -143,6 +140,7 @@ func TestMultiMetricsEngine(t *testing.T) {
 	metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5)
 	metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6)
 
+	metricsEngine.RecordAdapterBuyerUIDScrubbed(openrtb_ext.BidderAppnexus)
 	metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus)
 
 	metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1))
@@ -150,14 +148,14 @@ func TestMultiMetricsEngine(t *testing.T) {
 	//Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels
 	VerifyMetrics(t, "RequestStatuses.OpenRTB2.OK", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusOK].Count(), 5)
 	VerifyMetrics(t, "RequestStatuses.AMP.OK", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusOK].Count(), 0)
-	VerifyMetrics(t, "RequestStatuses.AMP.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusBlacklisted].Count(), 1)
+	VerifyMetrics(t, "RequestStatuses.AMP.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeAMP][metrics.RequestStatusBlockedApp].Count(), 1)
 	VerifyMetrics(t, "RequestStatuses.Video.OK", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusOK].Count(), 0)
 	VerifyMetrics(t, "RequestStatuses.Video.Error", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusErr].Count(), 0)
 	VerifyMetrics(t, "RequestStatuses.Video.BadInput", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBadInput].Count(), 0)
-	VerifyMetrics(t, "RequestStatuses.Video.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBlacklisted].Count(), 1)
+	VerifyMetrics(t, "RequestStatuses.Video.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusBlockedApp].Count(), 1)
 	VerifyMetrics(t, "RequestStatuses.OpenRTB2.Error", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusErr].Count(), 0)
 	VerifyMetrics(t, "RequestStatuses.OpenRTB2.BadInput", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBadInput].Count(), 0)
-	VerifyMetrics(t, "RequestStatuses.OpenRTB2.BlacklistedAcctOrApp", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBlacklisted].Count(), 0)
+	VerifyMetrics(t, "RequestStatuses.OpenRTB2.BlockedApp", goEngine.RequestStatuses[metrics.ReqTypeORTB2Web][metrics.RequestStatusBlockedApp].Count(), 0)
 
 	VerifyMetrics(t, "ImpsTypeBanner", goEngine.ImpsTypeBanner.Count(), 5)
 	VerifyMetrics(t, "ImpsTypeVideo", goEngine.ImpsTypeVideo.Count(), 3)
@@ -188,6 +186,7 @@ func TestMultiMetricsEngine(t *testing.T) {
 	VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5)
 	VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6)
 
+	VerifyMetrics(t, "AdapterMetrics.appNexus.BuyerUIDScrubbed", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].BuyerUIDScrubbed.Count(), 1)
 	VerifyMetrics(t, "AdapterMetrics.appNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[strings.ToLower(string(openrtb_ext.BidderAppnexus))].GDPRRequestBlocked.Count(), 1)
 
 	// verify that each module has its own metric recorded
diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go
index 05529220f16..9ca6505aa9e 100644
--- a/metrics/go_metrics_test.go
+++ b/metrics/go_metrics_test.go
@@ -73,7 +73,7 @@ func TestNewMetrics(t *testing.T) {
 	ensureContains(t, registry, "syncer.foo.request.ok", m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK])
 	ensureContains(t, registry, "syncer.foo.request.privacy_blocked", m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked])
 	ensureContains(t, registry, "syncer.foo.request.already_synced", m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced])
-	ensureContains(t, registry, "syncer.foo.request.type_not_supported", m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported])
+	ensureContains(t, registry, "syncer.foo.request.rejected_by_filter", m.SyncerRequestsMeter["foo"][SyncerCookieSyncRejectedByFilter])
 	ensureContains(t, registry, "syncer.foo.set.ok", m.SyncerSetsMeter["foo"][SyncerSetUidOK])
 	ensureContains(t, registry, "syncer.foo.set.cleared", m.SyncerSetsMeter["foo"][SyncerSetUidCleared])
 
@@ -864,7 +864,7 @@ func TestRecordSyncerRequest(t *testing.T) {
 	assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncOK].Count(), int64(1))
 	assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncPrivacyBlocked].Count(), int64(0))
 	assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncAlreadySynced].Count(), int64(0))
-	assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncTypeNotSupported].Count(), int64(0))
+	assert.Equal(t, m.SyncerRequestsMeter["foo"][SyncerCookieSyncRejectedByFilter].Count(), int64(0))
 }
 
 func TestRecordSetUid(t *testing.T) {
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 7d3dc819341..c254fd3ee16 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -234,7 +234,7 @@ const (
 	RequestStatusBadInput         RequestStatus = "badinput"
 	RequestStatusErr              RequestStatus = "err"
 	RequestStatusNetworkErr       RequestStatus = "networkerr"
-	RequestStatusBlacklisted      RequestStatus = "blacklistedacctorapp"
+	RequestStatusBlockedApp       RequestStatus = "blockedapp"
 	RequestStatusQueueTimeout     RequestStatus = "queuetimeout"
 	RequestStatusAccountConfigErr RequestStatus = "acctconfigerr"
 )
@@ -245,7 +245,7 @@ func RequestStatuses() []RequestStatus {
 		RequestStatusBadInput,
 		RequestStatusErr,
 		RequestStatusNetworkErr,
-		RequestStatusBlacklisted,
+		RequestStatusBlockedApp,
 		RequestStatusQueueTimeout,
 		RequestStatusAccountConfigErr,
 	}
@@ -361,7 +361,7 @@ const (
 	SyncerCookieSyncOK               SyncerCookieSyncStatus = "ok"
 	SyncerCookieSyncPrivacyBlocked   SyncerCookieSyncStatus = "privacy_blocked"
 	SyncerCookieSyncAlreadySynced    SyncerCookieSyncStatus = "already_synced"
-	SyncerCookieSyncTypeNotSupported SyncerCookieSyncStatus = "type_not_supported"
+	SyncerCookieSyncRejectedByFilter SyncerCookieSyncStatus = "rejected_by_filter"
 )
 
 // SyncerRequestStatuses returns possible syncer statuses.
@@ -370,7 +370,7 @@ func SyncerRequestStatuses() []SyncerCookieSyncStatus {
 		SyncerCookieSyncOK,
 		SyncerCookieSyncPrivacyBlocked,
 		SyncerCookieSyncAlreadySynced,
-		SyncerCookieSyncTypeNotSupported,
+		SyncerCookieSyncRejectedByFilter,
 	}
 }
 
diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go
index a74c8b6c0fa..15f8c3c23a5 100644
--- a/metrics/prometheus/prometheus_test.go
+++ b/metrics/prometheus/prometheus_test.go
@@ -141,7 +141,7 @@ func TestConnectionMetrics(t *testing.T) {
 func TestRequestMetric(t *testing.T) {
 	m := createMetricsForTesting()
 	requestType := metrics.ReqTypeORTB2Web
-	requestStatus := metrics.RequestStatusBlacklisted
+	requestStatus := metrics.RequestStatusBlockedApp
 
 	m.RecordRequest(metrics.Labels{
 		RType:         requestType,
@@ -285,7 +285,7 @@ func TestRequestMetricWithoutCookie(t *testing.T) {
 	performTest := func(m *Metrics, cookieFlag metrics.CookieFlag) {
 		m.RecordRequest(metrics.Labels{
 			RType:         requestType,
-			RequestStatus: metrics.RequestStatusBlacklisted,
+			RequestStatus: metrics.RequestStatusBlockedApp,
 			CookieFlag:    cookieFlag,
 		})
 	}
@@ -337,7 +337,7 @@ func TestAccountMetric(t *testing.T) {
 	performTest := func(m *Metrics, pubID string) {
 		m.RecordRequest(metrics.Labels{
 			RType:         metrics.ReqTypeORTB2Web,
-			RequestStatus: metrics.RequestStatusBlacklisted,
+			RequestStatus: metrics.RequestStatusBlockedApp,
 			PubID:         pubID,
 		})
 	}
@@ -1235,7 +1235,7 @@ func TestRecordSyncerRequestMetric(t *testing.T) {
 			label:  "already_synced",
 		},
 		{
-			status: metrics.SyncerCookieSyncTypeNotSupported,
+			status: metrics.SyncerCookieSyncRejectedByFilter,
 			label:  "type_not_supported",
 		},
 	}
diff --git a/modules/builder.go b/modules/builder.go
index e5d04e149af..36ac5589add 100644
--- a/modules/builder.go
+++ b/modules/builder.go
@@ -1,6 +1,7 @@
 package modules
 
 import (
+	fiftyonedegreesDevicedetection "github.com/prebid/prebid-server/v2/modules/fiftyonedegrees/devicedetection"
 	prebidOrtb2blocking "github.com/prebid/prebid-server/v2/modules/prebid/ortb2blocking"
 )
 
@@ -8,6 +9,9 @@ import (
 // vendor and module names are chosen based on the module directory name
 func builders() ModuleBuilders {
 	return ModuleBuilders{
+		"fiftyonedegrees": {
+			"devicedetection": fiftyonedegreesDevicedetection.Builder,
+		},
 		"prebid": {
 			"ortb2blocking": prebidOrtb2blocking.Builder,
 		},
diff --git a/modules/fiftyonedegrees/devicedetection/README.md b/modules/fiftyonedegrees/devicedetection/README.md
new file mode 100644
index 00000000000..645fb407fe5
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/README.md
@@ -0,0 +1,255 @@
+## Overview
+
+The 51Degrees module enriches an incoming OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/_device_detection__overview.html).
+
+The module sets the following fields of the device object: `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio` - interested bidder adapters may use these fields as needed.  In addition the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID which can be rapidly looked up in on premise data exposing over 250 properties including the device age, chip set, codec support, and price, operating system and app/browser versions, age, and embedded features.
+
+## Operation Details
+
+### Evidence
+
+The module uses `device.ua` (User Agent) and `device.sua` (Structured User Agent) provided in the oRTB request payload as input (or 'evidence' in 51Degrees terminology).  There is a fallback to the corresponding HTTP request headers if any of these are not present in the oRTB payload - in particular: `User-Agent` and `Sec-CH-UA-*` (aka User-Agent Client Hints).  To make sure Prebid.js sends Structured User Agent in the oRTB payload - we strongly advice publishers to enable [First Party Data Enrichment module](dev-docs/modules/enrichmentFpdModule.html) for their wrappers and specify
+
+```js
+pbjs.setConfig({
+    firstPartyData: {
+        uaHints: [
+          'architecture',
+          'model',
+          'platform',
+          'platformVersion',
+          'fullVersionList',
+        ]
+    }
+})
+```
+
+### Data File Updates
+
+The module operates **fully autonomously and does not make any requests to any cloud services in real time to do device detection**. This is an [on-premise data](https://51degrees.com/developers/deployment-options/on-premise-data) deployment in 51Degrees terminology. The module operates using a local data file that is loaded into memory fully or partially during operation. The data file is occasionally updated to accomodate new devices, so it is recommended to enable automatic data updates in the module configuration. Alternatively `watch_file_system` option can be used and the file may be downloaded and replaced on disk manually. See the configuration options below.
+
+## Setup
+
+The 51Degrees module operates using a data file. You can get started with a free Lite data file that can be downloaded here: [51Degrees-LiteV4.1.hash](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash).  The Lite file is capable of detecting limited device information, so if you need in-depth device data, please contact 51Degrees to obtain a license: [https://51degrees.com/contact-us](https://51degrees.com/contact-us?ContactReason=Free%20Trial).
+
+Put the data file in a file system location writable by the system account that is running the Prebid Server module and specify that directory location in the configuration parameters. The location needs to be writable if you would like to enable [automatic data file updates](https://51degrees.com/documentation/_features__automatic_datafile_updates.html).
+
+### Execution Plan
+
+This module supports running at two stages:
+
+* entrypoint: this is where incoming requests are parsed and device detection evidences are extracted.
+* raw-auction-request: this is where outgoing auction requests to each bidder are enriched with the device detection data
+
+We recommend defining the execution plan right in the account config
+so the module is only invoked for specific accounts. See below for an example.
+
+### Global Config
+
+There is no host-company level config for this module.
+
+### Account-Level Config
+
+To start using current module in PBS-Go you have to enable module and add `fiftyone-devicedetection-entrypoint-hook` and `fiftyone-devicedetection-raw-auction-request-hook` into hooks execution plan inside your config file:
+Here's a general template for the account config used in PBS-Go:
+
+```json
+{
+  "hooks": {
+    "enabled":true,
+    "modules": {
+      "fiftyonedegrees": {
+        "devicedetection": {
+          "enabled": true,
+          "make_temp_copy": true,
+          "data_file": {
+            "path": "path/to/51Degrees-LiteV4.1.hash",
+            "update": {
+              "auto": true,
+              "url": "<optional custom URL>",
+              "polling_interval": 1800,
+              "license_key": "<your_license_key>",
+              "product": "V4Enterprise",
+              "watch_file_system": "true",
+              "on_startup": true
+            }
+          }
+        }
+      },
+      "host_execution_plan": {
+        "endpoints": {
+          "/openrtb2/auction": {
+            "stages": {
+              "entrypoint": {
+                "groups": [
+                  {
+                    "timeout": 10,
+                    "hook_sequence": [
+                      {
+                        "module_code": "fiftyonedegrees.devicedetection",
+                        "hook_impl_code": "fiftyone-devicedetection-entrypoint-hook"
+                      }
+                    ]
+                  }
+                ]
+              },
+              "raw_auction_request": {
+                "groups": [
+                  {
+                    "timeout": 10,
+                    "hook_sequence": [
+                      {
+                        "module_code": "fiftyonedegrees.devicedetection",
+                        "hook_impl_code": "fiftyone-devicedetection-raw-auction-request-hook"
+                      }
+                    ]
+                  }
+                ]
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+```
+
+The same config in YAML format:
+```yaml
+hooks:
+  enabled: true
+  modules:
+    fiftyonedegrees:
+      devicedetection:
+        enabled: true
+        make_temp_copy: true
+        data_file:
+          path: path/to/51Degrees-LiteV4.1.hash
+          update:
+            auto: true
+            url: "<optional custom URL>"
+            polling_interval: 1800
+            license_key: "<your_license_key>"
+            product: V4Enterprise
+            watch_file_system: 'true'
+    host_execution_plan:
+      endpoints:
+        "/openrtb2/auction":
+          stages:
+            entrypoint:
+              groups:
+                - timeout: 10
+                  hook_sequence:
+                    - module_code: fiftyonedegrees.devicedetection
+                      hook_impl_code: fiftyone-devicedetection-entrypoint-hook
+            raw_auction_request:
+              groups:
+                - timeout: 10
+                  hook_sequence:
+                    - module_code: fiftyonedegrees.devicedetection
+                      hook_impl_code: fiftyone-devicedetection-raw-auction-request-hook
+```
+
+Note that at a minimum (besides adding to the host_execution_plan) you need to enable the module and specify a path to the data file in the configuration.
+Sample module enablement configuration in JSON and YAML formats:
+
+```json
+{
+  "modules": {
+    "fiftyonedegrees": {
+      "devicedetection": {
+        "enabled": true,
+        "data_file": {
+          "path": "path/to/51Degrees-LiteV4.1.hash"
+        }
+      }
+    }
+  }
+}
+```
+
+```yaml
+  modules:
+    fiftyonedegrees:
+      devicedetection: 
+        enabled: true
+        data_file:
+          path: "/path/to/51Degrees-LiteV4.1.hash"
+```
+
+## Module Configuration Parameters
+
+The parameter names are specified with full path using dot-notation.  F.e. `section_name` .`sub_section` .`param_name` would result in this nesting in the JSON configuration:
+
+```json
+{
+  "section_name": {
+    "sub_section": {
+      "param_name": "param-value"
+    }
+  }
+}
+```
+
+| Param Name | Required| Type | Default  value | Description |
+|:-------|:------|:------|:------|:---------------------------------------|
+| `account_filter` .`allow_list`  |  No | list of strings | [] (empty list) | A list of account IDs that are allowed to use this module - only relevant if enabled globally for the host. If empty, all accounts are allowed. Full-string match is performed (whitespaces and capitalization matter). |
+| `data_file` .`path`  |  **Yes** | string | null |The full path to the device detection data file. Sample file can be downloaded from [data repo on GitHub](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash), or get an Enterprise data file [here](https://51degrees.com/pricing). |
+| `data_file` .`make_temp_copy` | No | boolean | true | If true, the engine will create a temporary copy of the data file rather than using the data file directly. |
+| `data_file` .`update` .`auto` | No | boolean | true | If enabled, the engine will periodically (at predefined time intervals - see `polling-interval` parameter) check if new data file is available. When the new data file is available engine downloads it and switches to it for device detection. If custom `url` is not specified `license_key` param is required. |
+| `data_file` .`update` .`on_startup` | No | boolean | false | If enabled, engine will check for the updated data file right away without waiting for the defined time interval. |
+| `data_file` .`update` .`url` | No | string | null | Configure the engine to check the specified URL for the availability of the updated data file. If not specified the [51Degrees distributor service](https://51degrees.com/documentation/4.4/_info__distributor.html) URL will be used, which requires a License Key. |
+| `data_file` .`update` .`license_key` | No | string | null | Required if `auto` is true and custom `url` is not specified. Allows to download the data file from the [51Degrees distributor service](https://51degrees.com/documentation/4.4/_info__distributor.html). |
+| `data_file` .`update` .`watch_file_system` | No | boolean | true | If enabled the engine will watch the data file path for any changes, and automatically reload the data file from disk once it is updated. |
+| `data_file` .`update` .`polling_interval` | No | int | 1800 | The time interval in seconds between consequent attempts to download an updated data file. Default = 1800 seconds = 30 minutes. |
+| `data_file` .`update` .`product`| No | string | `V4Enterprise` | Set the Product used when checking for new device detection data files. A Product is exclusive to the 51Degrees paid service. Please see options [here](https://51degrees.com/documentation/_info__distributor.html). |
+| `performance` .`profile` | No | string | `Balanced` | `performance.*` parameters are related to the tradeoffs between speed of device detection and RAM consumption or accuracy. `profile` dictates the proportion between the use of the RAM (the more RAM used - the faster is the device detection) and reads from disk (less RAM but slower device detection). Must be one of: `LowMemory`, `MaxPerformance`, `HighPerformance`, `Balanced`, `BalancedTemp`, `InMemory`. Defaults to `Balanced`.  |
+| `performance` .`concurrency` | No | int | 10 |  Specify the expected number of concurrent operations that engine does. This sets the concurrency of the internal caches to avoid excessive locking. Default: 10.  |
+| `performance` .`difference` | No | int | 0 |  Set the maximum difference to allow when processing evidence (HTTP headers). The meaning is the difference in hash value between the hash that was found, and the hash that is being searched for. By default this is 0. For more information see [51Degrees documentation](https://51degrees.com/documentation/_device_detection__hash.html).  |
+| `performance` .`drift` | No | int | 0 |  Set the maximum drift to allow when matching hashes. If the drift is exceeded, the result is considered invalid and values will not be returned. By default this is 0. For more information see [51Degrees documentation](https://51degrees.com/documentation/_device_detection__hash.html).  |
+| `performance` .`allow_unmatched` | No | boolean | false |  If set to false, a non-matching evidence will result in properties with no values set. If set to true, a non-matching evidence will cause the 'default profiles' to be returned. This means that properties will always have values (i.e. no need to check .hasValue) but some may be inaccurate. By default, this is false. |
+
+## Running the demo
+
+1. Download dependencies:
+```bash
+go mod download
+```
+
+2. Replace the original config file `pbs.json` (placed in the repository root or in `/etc/config`) with the sample [config file](sample/pbs.json):
+```
+cp modules/fiftyonedegrees/devicedetection/sample/pbs.json pbs.json
+```
+
+3. Download `51Degrees-LiteV4.1.hash` from [[GitHub](https://github.com/51Degrees/device-detection-data/blob/main/51Degrees-LiteV4.1.hash)] and put it in the project root directory.
+
+```bash
+curl -o 51Degrees-LiteV4.1.hash -L https://github.com/51Degrees/device-detection-data/raw/main/51Degrees-LiteV4.1.hash
+```
+
+4. Create a directory for sample stored requests (needed for the server to run):
+```bash
+mkdir -p sample/stored
+```
+
+5. Start the server:
+```bash
+go run main.go
+```
+
+6. Run sample request:
+```bash
+curl \
+--header "Content-Type: application/json" \
+http://localhost:8000/openrtb2/auction \
+--data @modules/fiftyonedegrees/devicedetection/sample/request_data.json
+```
+
+7. Observe the `device` object get enriched with `devicetype`, `os`, `osv`, `w`, `h` and `ext.fiftyonedegrees_deviceId`.
+
+## Maintainer contacts
+
+Any suggestions or questions can be directed to [support@51degrees.com](support@51degrees.com) e-mail.
+
+Or just open new [issue](https://github.com/prebid/prebid-server/issues/new) or [pull request](https://github.com/prebid/prebid-server/pulls) in this repository.
diff --git a/modules/fiftyonedegrees/devicedetection/account_info_extractor.go b/modules/fiftyonedegrees/devicedetection/account_info_extractor.go
new file mode 100644
index 00000000000..2a5168cfe0c
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/account_info_extractor.go
@@ -0,0 +1,37 @@
+package devicedetection
+
+import (
+	"github.com/tidwall/gjson"
+)
+
+type accountInfo struct {
+	Id string
+}
+
+type accountInfoExtractor struct{}
+
+func newAccountInfoExtractor() accountInfoExtractor {
+	return accountInfoExtractor{}
+}
+
+// extract extracts the account information from the payload
+// The account information is extracted from the publisher id or site publisher id
+func (x accountInfoExtractor) extract(payload []byte) *accountInfo {
+	if payload == nil {
+		return nil
+	}
+
+	publisherResult := gjson.GetBytes(payload, "app.publisher.id")
+	if publisherResult.Exists() {
+		return &accountInfo{
+			Id: publisherResult.String(),
+		}
+	}
+	publisherResult = gjson.GetBytes(payload, "site.publisher.id")
+	if publisherResult.Exists() {
+		return &accountInfo{
+			Id: publisherResult.String(),
+		}
+	}
+	return nil
+}
diff --git a/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go b/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go
new file mode 100644
index 00000000000..2d32f7915b5
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/account_info_extractor_test.go
@@ -0,0 +1,74 @@
+package devicedetection
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	siteRequestPayload = []byte(`
+		{
+			"site": {
+				"publisher": {
+					"id": "p-bid-config-test-005"
+				}
+			}
+		}
+	`)
+
+	mobileRequestPayload = []byte(`
+		{
+			"app": {
+				"publisher": {
+					"id": "p-bid-config-test-005"
+				}
+			}
+		}
+	`)
+
+	emptyPayload = []byte(`{}`)
+)
+
+func TestPublisherIdExtraction(t *testing.T) {
+	tests := []struct {
+		name      string
+		payload   []byte
+		expected  string
+		expectNil bool
+	}{
+		{
+			name:     "SiteRequest",
+			payload:  siteRequestPayload,
+			expected: "p-bid-config-test-005",
+		},
+		{
+			name:     "MobileRequest",
+			payload:  mobileRequestPayload,
+			expected: "p-bid-config-test-005",
+		},
+		{
+			name:      "EmptyPublisherId",
+			payload:   emptyPayload,
+			expectNil: true,
+		},
+		{
+			name:      "EmptyPayload",
+			payload:   nil,
+			expectNil: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			extractor := newAccountInfoExtractor()
+			accountInfo := extractor.extract(tt.payload)
+
+			if tt.expectNil {
+				assert.Nil(t, accountInfo)
+			} else {
+				assert.Equal(t, tt.expected, accountInfo.Id)
+			}
+		})
+	}
+}
diff --git a/modules/fiftyonedegrees/devicedetection/account_validator.go b/modules/fiftyonedegrees/devicedetection/account_validator.go
new file mode 100644
index 00000000000..fdff92531a7
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/account_validator.go
@@ -0,0 +1,28 @@
+package devicedetection
+
+import "slices"
+
+// defaultAccountValidator is a struct that contains an accountInfoExtractor
+// and is used to validate if an account is allowed
+type defaultAccountValidator struct {
+	AccountExtractor accountInfoExtractor
+}
+
+func newAccountValidator() *defaultAccountValidator {
+	return &defaultAccountValidator{
+		AccountExtractor: newAccountInfoExtractor(),
+	}
+}
+
+func (x defaultAccountValidator) isAllowed(cfg config, req []byte) bool {
+	if len(cfg.AccountFilter.AllowList) == 0 {
+		return true
+	}
+
+	accountInfo := x.AccountExtractor.extract(req)
+	if accountInfo != nil && slices.Contains(cfg.AccountFilter.AllowList, accountInfo.Id) {
+		return true
+	}
+
+	return false
+}
diff --git a/modules/fiftyonedegrees/devicedetection/account_validator_test.go b/modules/fiftyonedegrees/devicedetection/account_validator_test.go
new file mode 100644
index 00000000000..25f99e3b796
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/account_validator_test.go
@@ -0,0 +1,71 @@
+package devicedetection
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/prebid/openrtb/v20/openrtb2"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsAllowed(t *testing.T) {
+	tests := []struct {
+		name           string
+		allowList      []string
+		expectedResult bool
+	}{
+		{
+			name:           "allowed",
+			allowList:      []string{"1001"},
+			expectedResult: true,
+		},
+		{
+			name:           "empty",
+			allowList:      []string{},
+			expectedResult: true,
+		},
+		{
+			name:           "disallowed",
+			allowList:      []string{"1002"},
+			expectedResult: false,
+		},
+		{
+			name:           "allow_list_is_nil",
+			allowList:      nil,
+			expectedResult: true,
+		},
+		{
+			name:           "allow_list_contains_multiple",
+			allowList:      []string{"1000", "1001", "1002"},
+			expectedResult: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			validator := newAccountValidator()
+			cfg := config{
+				AccountFilter: accountFilter{AllowList: test.allowList},
+			}
+
+			res := validator.isAllowed(
+				cfg, toBytes(
+					&openrtb2.BidRequest{
+						App: &openrtb2.App{
+							Publisher: &openrtb2.Publisher{
+								ID: "1001",
+							},
+						},
+					},
+				),
+			)
+			assert.Equal(t, test.expectedResult, res)
+		})
+	}
+}
+
+func toBytes(v interface{}) []byte {
+	res, _ := json.Marshal(v)
+	return res
+}
diff --git a/modules/fiftyonedegrees/devicedetection/config.go b/modules/fiftyonedegrees/devicedetection/config.go
new file mode 100644
index 00000000000..a5519026791
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/config.go
@@ -0,0 +1,80 @@
+package devicedetection
+
+import (
+	"encoding/json"
+	"os"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/pkg/errors"
+
+	"github.com/prebid/prebid-server/v2/util/jsonutil"
+)
+
+type config struct {
+	DataFile      dataFile      `json:"data_file"`
+	AccountFilter accountFilter `json:"account_filter"`
+	Performance   performance   `json:"performance"`
+}
+
+type dataFile struct {
+	Path         string         `json:"path"`
+	Update       dataFileUpdate `json:"update"`
+	MakeTempCopy *bool          `json:"make_temp_copy"`
+}
+
+type dataFileUpdate struct {
+	Auto            bool   `json:"auto"`
+	Url             string `json:"url"`
+	License         string `json:"license_key"`
+	PollingInterval int    `json:"polling_interval"`
+	Product         string `json:"product"`
+	WatchFileSystem *bool  `json:"watch_file_system"`
+	OnStartup       bool   `json:"on_startup"`
+}
+
+type accountFilter struct {
+	AllowList []string `json:"allow_list"`
+}
+
+type performance struct {
+	Profile        string `json:"profile"`
+	Concurrency    *int   `json:"concurrency"`
+	Difference     *int   `json:"difference"`
+	AllowUnmatched *bool  `json:"allow_unmatched"`
+	Drift          *int   `json:"drift"`
+}
+
+var performanceProfileMap = map[string]dd.PerformanceProfile{
+	"Default":         dd.Default,
+	"LowMemory":       dd.LowMemory,
+	"BalancedTemp":    dd.BalancedTemp,
+	"Balanced":        dd.Balanced,
+	"HighPerformance": dd.HighPerformance,
+	"InMemory":        dd.InMemory,
+}
+
+func (c *config) getPerformanceProfile() dd.PerformanceProfile {
+	mappedResult, ok := performanceProfileMap[c.Performance.Profile]
+	if !ok {
+		return dd.Default
+	}
+
+	return mappedResult
+}
+
+func parseConfig(data json.RawMessage) (config, error) {
+	var cfg config
+	if err := jsonutil.UnmarshalValid(data, &cfg); err != nil {
+		return cfg, errors.Wrap(err, "failed to parse config")
+	}
+	return cfg, nil
+}
+
+func validateConfig(cfg config) error {
+	_, err := os.Stat(cfg.DataFile.Path)
+	if err != nil {
+		return errors.Wrap(err, "error opening hash file path")
+	}
+
+	return nil
+}
diff --git a/modules/fiftyonedegrees/devicedetection/config_test.go b/modules/fiftyonedegrees/devicedetection/config_test.go
new file mode 100644
index 00000000000..e2478d82b7d
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/config_test.go
@@ -0,0 +1,119 @@
+package devicedetection
+
+import (
+	"os"
+	"testing"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseConfig(t *testing.T) {
+	cfgRaw := []byte(`{ 
+		"enabled": true,
+		"data_file": {
+            "path": "path/to/51Degrees-LiteV4.1.hash",
+            "update": {
+				"auto": true,
+				"url": "https://my.datafile.com/datafile.gz",
+				"polling_interval": 3600,
+				"license_key": "your_license_key",
+				"product": "V4Enterprise",
+				"on_startup": true
+            }
+		},
+		"account_filter": {"allow_list": ["123"]},
+		"performance": {
+			"profile": "default",
+			"concurrency": 1,
+			"difference": 1,
+			"allow_unmatched": true,
+			"drift": 1	
+		}
+	}`)
+
+	cfg, err := parseConfig(cfgRaw)
+
+	assert.NoError(t, err)
+
+	assert.Equal(t, cfg.DataFile.Path, "path/to/51Degrees-LiteV4.1.hash")
+	assert.True(t, cfg.DataFile.Update.Auto)
+	assert.Equal(t, cfg.DataFile.Update.Url, "https://my.datafile.com/datafile.gz")
+	assert.Equal(t, cfg.DataFile.Update.PollingInterval, 3600)
+	assert.Equal(t, cfg.DataFile.Update.License, "your_license_key")
+	assert.Equal(t, cfg.DataFile.Update.Product, "V4Enterprise")
+	assert.True(t, cfg.DataFile.Update.OnStartup)
+	assert.Equal(t, cfg.AccountFilter.AllowList, []string{"123"})
+	assert.Equal(t, cfg.Performance.Profile, "default")
+	assert.Equal(t, *cfg.Performance.Concurrency, 1)
+	assert.Equal(t, *cfg.Performance.Difference, 1)
+	assert.True(t, *cfg.Performance.AllowUnmatched)
+	assert.Equal(t, *cfg.Performance.Drift, 1)
+	assert.Equal(t, cfg.getPerformanceProfile(), dd.Default)
+}
+
+func TestValidateConfig(t *testing.T) {
+	file, err := os.Create("test-validate-config.hash")
+	if err != nil {
+		t.Errorf("Failed to create file: %v", err)
+	}
+	defer file.Close()
+	defer os.Remove("test-validate-config.hash")
+
+	cfgRaw := []byte(`{ 
+		"enabled": true,
+		"data_file": {
+			"path": "test-validate-config.hash",
+			"update": {
+				"auto": true,
+				"url": "https://my.datafile.com/datafile.gz",
+				"polling_interval": 3600,
+				"licence_key": "your_licence_key",
+				"product": "V4Enterprise"
+			}
+		},
+		"account_filter": {"allow_list": ["123"]},
+		"performance": {
+			"profile": "default",
+			"concurrency": 1,
+			"difference": 1,
+			"allow_unmatched": true,
+			"drift": 1	
+		}
+	}`)
+
+	cfg, err := parseConfig(cfgRaw)
+	assert.NoError(t, err)
+
+	err = validateConfig(cfg)
+	assert.NoError(t, err)
+
+}
+
+func TestInvalidPerformanceProfile(t *testing.T) {
+	cfgRaw := []byte(`{ 
+		"enabled": true,
+		"data_file": {
+			"path": "test-validate-config.hash",
+			"update": {
+				"auto": true,
+				"url": "https://my.datafile.com/datafile.gz",
+				"polling_interval": 3600,
+				"licence_key": "your_licence_key",
+				"product": "V4Enterprise"
+			}
+		},
+		"account_filter": {"allow_list": ["123"]},
+		"performance": {
+			"profile": "123",
+			"concurrency": 1,
+			"difference": 1,
+			"allow_unmatched": true,
+			"drift": 1	
+		}
+	}`)
+	cfg, err := parseConfig(cfgRaw)
+	assert.NoError(t, err)
+
+	assert.Equal(t, cfg.getPerformanceProfile(), dd.Default)
+}
diff --git a/modules/fiftyonedegrees/devicedetection/context.go b/modules/fiftyonedegrees/devicedetection/context.go
new file mode 100644
index 00000000000..3c10dd2f393
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/context.go
@@ -0,0 +1,8 @@
+package devicedetection
+
+// Context keys for device detection
+const (
+	evidenceFromHeadersCtxKey = "evidence_from_headers"
+	evidenceFromSuaCtxKey     = "evidence_from_sua"
+	ddEnabledCtxKey           = "dd_enabled"
+)
diff --git a/modules/fiftyonedegrees/devicedetection/device_detector.go b/modules/fiftyonedegrees/devicedetection/device_detector.go
new file mode 100644
index 00000000000..8369d343d34
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/device_detector.go
@@ -0,0 +1,157 @@
+package devicedetection
+
+import (
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/51Degrees/device-detection-go/v4/onpremise"
+	"github.com/pkg/errors"
+)
+
+type engine interface {
+	Process(evidences []onpremise.Evidence) (*dd.ResultsHash, error)
+	GetHttpHeaderKeys() []dd.EvidenceKey
+}
+
+type extractor interface {
+	extract(results Results, ua string) (*deviceInfo, error)
+}
+
+type defaultDeviceDetector struct {
+	cfg                 *dd.ConfigHash
+	deviceInfoExtractor extractor
+	engine              engine
+}
+
+func newDeviceDetector(cfg *dd.ConfigHash, moduleConfig *config) (*defaultDeviceDetector, error) {
+	engineOptions := buildEngineOptions(moduleConfig, cfg)
+
+	ddEngine, err := onpremise.New(
+		engineOptions...,
+	)
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to create onpremise engine.")
+	}
+
+	deviceDetector := &defaultDeviceDetector{
+		engine:              ddEngine,
+		cfg:                 cfg,
+		deviceInfoExtractor: newDeviceInfoExtractor(),
+	}
+
+	return deviceDetector, nil
+}
+
+func buildEngineOptions(moduleConfig *config, configHash *dd.ConfigHash) []onpremise.EngineOptions {
+	options := []onpremise.EngineOptions{
+		onpremise.WithDataFile(moduleConfig.DataFile.Path),
+	}
+
+	options = append(
+		options,
+		onpremise.WithProperties([]string{
+			"HardwareVendor",
+			"HardwareName",
+			"DeviceType",
+			"PlatformVendor",
+			"PlatformName",
+			"PlatformVersion",
+			"BrowserVendor",
+			"BrowserName",
+			"BrowserVersion",
+			"ScreenPixelsWidth",
+			"ScreenPixelsHeight",
+			"PixelRatio",
+			"Javascript",
+			"GeoLocation",
+			"HardwareModel",
+			"HardwareFamily",
+			"HardwareModelVariants",
+			"ScreenInchesHeight",
+			"IsCrawler",
+		}),
+	)
+
+	options = append(
+		options,
+		onpremise.WithConfigHash(configHash),
+	)
+
+	if moduleConfig.DataFile.MakeTempCopy != nil {
+		options = append(
+			options,
+			onpremise.WithTempDataCopy(*moduleConfig.DataFile.MakeTempCopy),
+		)
+	}
+
+	dataUpdateOptions := []onpremise.EngineOptions{
+		onpremise.WithAutoUpdate(moduleConfig.DataFile.Update.Auto),
+	}
+
+	if moduleConfig.DataFile.Update.Url != "" {
+		dataUpdateOptions = append(
+			dataUpdateOptions,
+			onpremise.WithDataUpdateUrl(
+				moduleConfig.DataFile.Update.Url,
+			),
+		)
+	}
+
+	if moduleConfig.DataFile.Update.PollingInterval > 0 {
+		dataUpdateOptions = append(
+			dataUpdateOptions,
+			onpremise.WithPollingInterval(
+				moduleConfig.DataFile.Update.PollingInterval,
+			),
+		)
+	}
+
+	if moduleConfig.DataFile.Update.License != "" {
+		dataUpdateOptions = append(
+			dataUpdateOptions,
+			onpremise.WithLicenseKey(moduleConfig.DataFile.Update.License),
+		)
+	}
+
+	if moduleConfig.DataFile.Update.Product != "" {
+		dataUpdateOptions = append(
+			dataUpdateOptions,
+			onpremise.WithProduct(moduleConfig.DataFile.Update.Product),
+		)
+	}
+
+	if moduleConfig.DataFile.Update.WatchFileSystem != nil {
+		dataUpdateOptions = append(
+			dataUpdateOptions,
+			onpremise.WithFileWatch(
+				*moduleConfig.DataFile.Update.WatchFileSystem,
+			),
+		)
+	}
+
+	dataUpdateOptions = append(
+		dataUpdateOptions,
+		onpremise.WithUpdateOnStart(moduleConfig.DataFile.Update.OnStartup),
+	)
+
+	options = append(
+		options,
+		dataUpdateOptions...,
+	)
+
+	return options
+}
+
+func (x defaultDeviceDetector) getSupportedHeaders() []dd.EvidenceKey {
+	return x.engine.GetHttpHeaderKeys()
+}
+
+func (x defaultDeviceDetector) getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error) {
+	results, err := x.engine.Process(evidence)
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to process evidence")
+	}
+	defer results.Free()
+
+	deviceInfo, err := x.deviceInfoExtractor.extract(results, ua)
+
+	return deviceInfo, err
+}
diff --git a/modules/fiftyonedegrees/devicedetection/device_detector_test.go b/modules/fiftyonedegrees/devicedetection/device_detector_test.go
new file mode 100644
index 00000000000..84d6ab28cc0
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/device_detector_test.go
@@ -0,0 +1,190 @@
+package devicedetection
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/51Degrees/device-detection-go/v4/onpremise"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func TestBuildEngineOptions(t *testing.T) {
+	cases := []struct {
+		cfgRaw []byte
+		length int
+	}{
+		{
+			cfgRaw: []byte(`{ 
+				"enabled": true,
+				"data_file": {
+					"path": "path/to/51Degrees-LiteV4.1.hash",
+					"update": {
+						"auto": true,
+						"url": "https://my.datafile.com/datafile.gz",
+						"polling_interval": 3600,
+						"license_key": "your_license_key",
+						"product": "V4Enterprise",
+						"watch_file_system": true,
+						"on_startup": true
+					},
+					"make_temp_copy": true
+				},
+				"account_filter": {"allow_list": ["123"]},
+				"performance": {
+					"profile": "default",
+					"concurrency": 1,
+					"difference": 1,
+					"allow_unmatched": true,
+					"drift": 1	
+				}
+			}`),
+			length: 11,
+			// data_file.path, data_file.update.auto:true, url, polling_interval, license_key, product, confighash, properties
+			// data_file.update.on_startup:true, data_file.update.watch_file_system:true, data_file.make_temp_copy:true
+		},
+		{
+			cfgRaw: []byte(`{ 
+				"enabled": true,
+				"data_file": {
+					"path": "path/to/51Degrees-LiteV4.1.hash"
+				},
+				"account_filter": {"allow_list": ["123"]},
+				"performance": {
+					"profile": "default",
+					"concurrency": 1,
+					"difference": 1,
+					"allow_unmatched": true,
+					"drift": 1	
+				}
+			}`),
+			length: 5, // data_file.update.auto:false, data_file.path, confighash, properties, data_file.update.on_startup:false
+		},
+	}
+
+	for _, c := range cases {
+		cfg, err := parseConfig(c.cfgRaw)
+		assert.NoError(t, err)
+		configHash := configHashFromConfig(&cfg)
+		options := buildEngineOptions(&cfg, configHash)
+		assert.Equal(t, c.length, len(options))
+	}
+}
+
+type engineMock struct {
+	mock.Mock
+}
+
+func (e *engineMock) Process(evidences []onpremise.Evidence) (*dd.ResultsHash, error) {
+	args := e.Called(evidences)
+	res := args.Get(0)
+	if res == nil {
+		return nil, args.Error(1)
+	}
+
+	return res.(*dd.ResultsHash), args.Error(1)
+}
+
+func (e *engineMock) GetHttpHeaderKeys() []dd.EvidenceKey {
+	args := e.Called()
+	return args.Get(0).([]dd.EvidenceKey)
+}
+
+type extractorMock struct {
+	mock.Mock
+}
+
+func (e *extractorMock) extract(results Results, ua string) (*deviceInfo, error) {
+	args := e.Called(results, ua)
+	return args.Get(0).(*deviceInfo), args.Error(1)
+}
+
+func TestGetDeviceInfo(t *testing.T) {
+	tests := []struct {
+		name           string
+		engineResponse *dd.ResultsHash
+		engineError    error
+		expectedResult *deviceInfo
+		expectedError  string
+	}{
+		{
+			name:           "Success_path",
+			engineResponse: &dd.ResultsHash{},
+			engineError:    nil,
+			expectedResult: &deviceInfo{
+				DeviceId: "123",
+			},
+			expectedError: "",
+		},
+		{
+			name:           "Error_path",
+			engineResponse: nil,
+			engineError:    fmt.Errorf("error"),
+			expectedResult: nil,
+			expectedError:  "Failed to process evidence: error",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			extractorM := &extractorMock{}
+			extractorM.On("extract", mock.Anything, mock.Anything).Return(
+				&deviceInfo{
+					DeviceId: "123",
+				}, nil,
+			)
+
+			engineM := &engineMock{}
+			engineM.On("Process", mock.Anything).Return(
+				tt.engineResponse, tt.engineError,
+			)
+
+			deviceDetector := defaultDeviceDetector{
+				cfg:                 nil,
+				deviceInfoExtractor: extractorM,
+				engine:              engineM,
+			}
+
+			result, err := deviceDetector.getDeviceInfo(
+				[]onpremise.Evidence{{
+					Prefix: dd.HttpEvidenceQuery,
+					Key:    "key",
+					Value:  "val",
+				}}, "ua",
+			)
+
+			if tt.expectedError == "" {
+				assert.NoError(t, err)
+				assert.NotNil(t, result)
+				assert.Equal(t, tt.expectedResult.DeviceId, result.DeviceId)
+			} else {
+				assert.Errorf(t, err, tt.expectedError)
+				assert.Nil(t, result)
+			}
+		})
+	}
+}
+
+func TestGetSupportedHeaders(t *testing.T) {
+	engineM := &engineMock{}
+
+	engineM.On("GetHttpHeaderKeys").Return(
+		[]dd.EvidenceKey{{
+			Key:    "key",
+			Prefix: dd.HttpEvidenceQuery,
+		}},
+	)
+
+	deviceDetector := defaultDeviceDetector{
+		cfg:                 nil,
+		deviceInfoExtractor: nil,
+		engine:              engineM,
+	}
+
+	result := deviceDetector.getSupportedHeaders()
+	assert.NotNil(t, result)
+	assert.Equal(t, len(result), 1)
+	assert.Equal(t, result[0].Key, "key")
+
+}
diff --git a/modules/fiftyonedegrees/devicedetection/device_info_extractor.go b/modules/fiftyonedegrees/devicedetection/device_info_extractor.go
new file mode 100644
index 00000000000..1c913e21696
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/device_info_extractor.go
@@ -0,0 +1,121 @@
+package devicedetection
+
+import (
+	"strconv"
+
+	"github.com/golang/glog"
+	"github.com/pkg/errors"
+)
+
+// deviceInfoExtractor is a struct that contains the methods to extract device information
+// from the results of the device detection
+type deviceInfoExtractor struct{}
+
+func newDeviceInfoExtractor() deviceInfoExtractor {
+	return deviceInfoExtractor{}
+}
+
+type Results interface {
+	ValuesString(string, string) (string, error)
+	HasValues(string) (bool, error)
+	DeviceId() (string, error)
+}
+
+type deviceInfoProperty string
+
+const (
+	deviceInfoHardwareVendor        deviceInfoProperty = "HardwareVendor"
+	deviceInfoHardwareName          deviceInfoProperty = "HardwareName"
+	deviceInfoDeviceType            deviceInfoProperty = "DeviceType"
+	deviceInfoPlatformVendor        deviceInfoProperty = "PlatformVendor"
+	deviceInfoPlatformName          deviceInfoProperty = "PlatformName"
+	deviceInfoPlatformVersion       deviceInfoProperty = "PlatformVersion"
+	deviceInfoBrowserVendor         deviceInfoProperty = "BrowserVendor"
+	deviceInfoBrowserName           deviceInfoProperty = "BrowserName"
+	deviceInfoBrowserVersion        deviceInfoProperty = "BrowserVersion"
+	deviceInfoScreenPixelsWidth     deviceInfoProperty = "ScreenPixelsWidth"
+	deviceInfoScreenPixelsHeight    deviceInfoProperty = "ScreenPixelsHeight"
+	deviceInfoPixelRatio            deviceInfoProperty = "PixelRatio"
+	deviceInfoJavascript            deviceInfoProperty = "Javascript"
+	deviceInfoGeoLocation           deviceInfoProperty = "GeoLocation"
+	deviceInfoHardwareModel         deviceInfoProperty = "HardwareModel"
+	deviceInfoHardwareFamily        deviceInfoProperty = "HardwareFamily"
+	deviceInfoHardwareModelVariants deviceInfoProperty = "HardwareModelVariants"
+	deviceInfoScreenInchesHeight    deviceInfoProperty = "ScreenInchesHeight"
+)
+
+func (x deviceInfoExtractor) extract(results Results, ua string) (*deviceInfo, error) {
+	hardwareVendor := x.getValue(results, deviceInfoHardwareVendor)
+	hardwareName := x.getValue(results, deviceInfoHardwareName)
+	deviceType := x.getValue(results, deviceInfoDeviceType)
+	platformVendor := x.getValue(results, deviceInfoPlatformVendor)
+	platformName := x.getValue(results, deviceInfoPlatformName)
+	platformVersion := x.getValue(results, deviceInfoPlatformVersion)
+	browserVendor := x.getValue(results, deviceInfoBrowserVendor)
+	browserName := x.getValue(results, deviceInfoBrowserName)
+	browserVersion := x.getValue(results, deviceInfoBrowserVersion)
+	screenPixelsWidth, _ := strconv.ParseInt(x.getValue(results, deviceInfoScreenPixelsWidth), 10, 64)
+	screenPixelsHeight, _ := strconv.ParseInt(x.getValue(results, deviceInfoScreenPixelsHeight), 10, 64)
+	pixelRatio, _ := strconv.ParseFloat(x.getValue(results, deviceInfoPixelRatio), 10)
+	javascript, _ := strconv.ParseBool(x.getValue(results, deviceInfoJavascript))
+	geoLocation, _ := strconv.ParseBool(x.getValue(results, deviceInfoGeoLocation))
+	deviceId, err := results.DeviceId()
+	if err != nil {
+		return nil, errors.Wrap(err, "Failed to get device id.")
+	}
+	hardwareModel := x.getValue(results, deviceInfoHardwareModel)
+	hardwareFamily := x.getValue(results, deviceInfoHardwareFamily)
+	hardwareModelVariants := x.getValue(results, deviceInfoHardwareModelVariants)
+	screenInchedHeight, _ := strconv.ParseFloat(x.getValue(results, deviceInfoScreenInchesHeight), 10)
+
+	p := &deviceInfo{
+		HardwareVendor:        hardwareVendor,
+		HardwareName:          hardwareName,
+		DeviceType:            deviceType,
+		PlatformVendor:        platformVendor,
+		PlatformName:          platformName,
+		PlatformVersion:       platformVersion,
+		BrowserVendor:         browserVendor,
+		BrowserName:           browserName,
+		BrowserVersion:        browserVersion,
+		ScreenPixelsWidth:     screenPixelsWidth,
+		ScreenPixelsHeight:    screenPixelsHeight,
+		PixelRatio:            pixelRatio,
+		Javascript:            javascript,
+		GeoLocation:           geoLocation,
+		UserAgent:             ua,
+		DeviceId:              deviceId,
+		HardwareModel:         hardwareModel,
+		HardwareFamily:        hardwareFamily,
+		HardwareModelVariants: hardwareModelVariants,
+		ScreenInchesHeight:    screenInchedHeight,
+	}
+
+	return p, nil
+}
+
+// function getValue return a value results for a property
+func (x deviceInfoExtractor) getValue(results Results, propertyName deviceInfoProperty) string {
+	// Get the values in string
+	value, err := results.ValuesString(
+		string(propertyName),
+		",",
+	)
+	if err != nil {
+		glog.Errorf("Failed to get results values string.")
+		return ""
+	}
+
+	hasValues, err := results.HasValues(string(propertyName))
+	if err != nil {
+		glog.Errorf("Failed to check if a matched value exists for property %s.\n", propertyName)
+		return ""
+	}
+
+	if !hasValues {
+		glog.Warningf("Property %s does not have a matched value.\n", propertyName)
+		return "Unknown"
+	}
+
+	return value
+}
diff --git a/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go b/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go
new file mode 100644
index 00000000000..197e3928602
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/device_info_extractor_test.go
@@ -0,0 +1,130 @@
+package devicedetection
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+type ResultsHashMock struct {
+	mock.Mock
+}
+
+func (m *ResultsHashMock) DeviceId() (string, error) {
+	return "", nil
+}
+
+func (m *ResultsHashMock) ValuesString(prop1 string, prop2 string) (string, error) {
+	args := m.Called(prop1, prop2)
+	return args.String(0), args.Error(1)
+}
+
+func (m *ResultsHashMock) HasValues(prop1 string) (bool, error) {
+	args := m.Called(prop1)
+	return args.Bool(0), args.Error(1)
+}
+
+func TestDeviceInfoExtraction(t *testing.T) {
+	results := &ResultsHashMock{}
+
+	extractor := newDeviceInfoExtractor()
+	mockValue(results, "HardwareName", "Macbook")
+	mockValues(results)
+
+	deviceInfo, _ := extractor.extract(results, "ua")
+	assert.NotNil(t, deviceInfo)
+
+	assert.Equal(t, deviceInfo.HardwareName, "Macbook")
+	assertDeviceInfo(t, deviceInfo)
+}
+
+func TestDeviceInfoExtractionNoProperty(t *testing.T) {
+	results := &ResultsHashMock{}
+
+	extractor := newDeviceInfoExtractor()
+	results.Mock.On("ValuesString", "HardwareName", ",").Return("", errors.New("Error"))
+	results.Mock.On("HasValues", "HardwareName").Return(true, nil)
+	mockValues(results)
+
+	deviceInfo, _ := extractor.extract(results, "ua")
+	assert.NotNil(t, deviceInfo)
+
+	assertDeviceInfo(t, deviceInfo)
+	assert.Equal(t, deviceInfo.HardwareName, "")
+}
+
+func TestDeviceInfoExtractionNoValue(t *testing.T) {
+	results := &ResultsHashMock{}
+
+	extractor := newDeviceInfoExtractor()
+	mockValues(results)
+	mockValue(results, "HardwareVendor", "Apple")
+
+	results.Mock.On("ValuesString", "HardwareName", ",").Return("Macbook", nil)
+	results.Mock.On("HasValues", "HardwareName").Return(false, nil)
+
+	deviceInfo, _ := extractor.extract(results, "ua")
+	assert.NotNil(t, deviceInfo)
+	assertDeviceInfo(t, deviceInfo)
+	assert.Equal(t, deviceInfo.HardwareName, "Unknown")
+}
+
+func TestDeviceInfoExtractionHasValueError(t *testing.T) {
+	results := &ResultsHashMock{}
+
+	extractor := newDeviceInfoExtractor()
+	mockValue(results, "HardwareVendor", "Apple")
+
+	results.Mock.On("ValuesString", "HardwareName", ",").Return("Macbook", nil)
+	results.Mock.On("HasValues", "HardwareName").Return(true, errors.New("error"))
+
+	mockValues(results)
+
+	deviceInfo, _ := extractor.extract(results, "ua")
+	assert.NotNil(t, deviceInfo)
+	assertDeviceInfo(t, deviceInfo)
+	assert.Equal(t, deviceInfo.HardwareName, "")
+}
+
+func mockValues(results *ResultsHashMock) {
+	mockValue(results, "HardwareVendor", "Apple")
+	mockValue(results, "DeviceType", "Desctop")
+	mockValue(results, "PlatformVendor", "Apple")
+	mockValue(results, "PlatformName", "MacOs")
+	mockValue(results, "PlatformVersion", "14")
+	mockValue(results, "BrowserVendor", "Google")
+	mockValue(results, "BrowserName", "Crome")
+	mockValue(results, "BrowserVersion", "12")
+	mockValue(results, "ScreenPixelsWidth", "1024")
+	mockValue(results, "ScreenPixelsHeight", "1080")
+	mockValue(results, "PixelRatio", "223")
+	mockValue(results, "Javascript", "true")
+	mockValue(results, "GeoLocation", "true")
+	mockValue(results, "HardwareModel", "Macbook")
+	mockValue(results, "HardwareFamily", "Macbook")
+	mockValue(results, "HardwareModelVariants", "Macbook")
+	mockValue(results, "ScreenInchesHeight", "12")
+}
+
+func assertDeviceInfo(t *testing.T, deviceInfo *deviceInfo) {
+	assert.Equal(t, deviceInfo.HardwareVendor, "Apple")
+	assert.Equal(t, deviceInfo.DeviceType, "Desctop")
+	assert.Equal(t, deviceInfo.PlatformVendor, "Apple")
+	assert.Equal(t, deviceInfo.PlatformName, "MacOs")
+	assert.Equal(t, deviceInfo.PlatformVersion, "14")
+	assert.Equal(t, deviceInfo.BrowserVendor, "Google")
+	assert.Equal(t, deviceInfo.BrowserName, "Crome")
+	assert.Equal(t, deviceInfo.BrowserVersion, "12")
+	assert.Equal(t, deviceInfo.ScreenPixelsWidth, int64(1024))
+	assert.Equal(t, deviceInfo.ScreenPixelsHeight, int64(1080))
+	assert.Equal(t, deviceInfo.PixelRatio, float64(223))
+	assert.Equal(t, deviceInfo.Javascript, true)
+	assert.Equal(t, deviceInfo.GeoLocation, true)
+}
+
+func mockValue(results *ResultsHashMock, name string, value string) {
+	results.Mock.On("ValuesString", name, ",").Return(value, nil)
+	results.Mock.On("HasValues", name).Return(true, nil)
+}
diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go
new file mode 100644
index 00000000000..1d67e1cdeed
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor.go
@@ -0,0 +1,118 @@
+package devicedetection
+
+import (
+	"net/http"
+
+	"github.com/51Degrees/device-detection-go/v4/onpremise"
+	"github.com/pkg/errors"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+)
+
+type defaultEvidenceExtractor struct {
+	valFromHeaders evidenceFromRequestHeadersExtractor
+	valFromSUA     evidenceFromSUAPayloadExtractor
+}
+
+func newEvidenceExtractor() *defaultEvidenceExtractor {
+	evidenceExtractor := &defaultEvidenceExtractor{
+		valFromHeaders: newEvidenceFromRequestHeadersExtractor(),
+		valFromSUA:     newEvidenceFromSUAPayloadExtractor(),
+	}
+
+	return evidenceExtractor
+}
+
+func (x *defaultEvidenceExtractor) fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence {
+	return x.valFromHeaders.extract(request, httpHeaderKeys)
+}
+
+func (x *defaultEvidenceExtractor) fromSuaPayload(payload []byte) []stringEvidence {
+	return x.valFromSUA.extract(payload)
+}
+
+// merge merges two slices of stringEvidence into one slice of stringEvidence
+func merge(val1, val2 []stringEvidence) []stringEvidence {
+	evidenceMap := make(map[string]stringEvidence)
+	for _, e := range val1 {
+		evidenceMap[e.Key] = e
+	}
+
+	for _, e := range val2 {
+		_, exists := evidenceMap[e.Key]
+		if !exists {
+			evidenceMap[e.Key] = e
+		}
+	}
+
+	evidence := make([]stringEvidence, 0)
+
+	for _, e := range evidenceMap {
+		evidence = append(evidence, e)
+	}
+
+	return evidence
+}
+
+func (x *defaultEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) {
+	if ctx == nil {
+		return nil, "", errors.New("context is nil")
+	}
+
+	suaStrings, err := x.getEvidenceStrings(ctx[evidenceFromSuaCtxKey])
+	if err != nil {
+		return nil, "", errors.Wrap(err, "error extracting sua evidence")
+	}
+	headerString, err := x.getEvidenceStrings(ctx[evidenceFromHeadersCtxKey])
+	if err != nil {
+		return nil, "", errors.Wrap(err, "error extracting header evidence")
+	}
+
+	// Merge evidence from headers and SUA, sua has higher priority
+	evidenceStrings := merge(suaStrings, headerString)
+
+	if len(evidenceStrings) > 0 {
+		userAgentE, exists := getEvidenceByKey(evidenceStrings, userAgentHeader)
+		if !exists {
+			return nil, "", errors.New("User-Agent not found")
+		}
+
+		evidence := x.extractEvidenceFromStrings(evidenceStrings)
+
+		return evidence, userAgentE.Value, nil
+	}
+
+	return nil, "", nil
+}
+
+func (x *defaultEvidenceExtractor) getEvidenceStrings(source interface{}) ([]stringEvidence, error) {
+	if source == nil {
+		return []stringEvidence{}, nil
+	}
+
+	evidenceStrings, ok := source.([]stringEvidence)
+	if !ok {
+		return nil, errors.New("bad cast to []stringEvidence")
+	}
+
+	return evidenceStrings, nil
+}
+
+func (x *defaultEvidenceExtractor) extractEvidenceFromStrings(strEvidence []stringEvidence) []onpremise.Evidence {
+	evidenceResult := make([]onpremise.Evidence, len(strEvidence))
+	for i, e := range strEvidence {
+		prefix := dd.HttpHeaderString
+		if e.Prefix == queryPrefix {
+			prefix = dd.HttpEvidenceQuery
+		}
+
+		evidenceResult[i] = onpremise.Evidence{
+			Prefix: prefix,
+			Key:    e.Key,
+			Value:  e.Value,
+		}
+	}
+
+	return evidenceResult
+}
diff --git a/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go
new file mode 100644
index 00000000000..9abdf799643
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/evidence_extractor_test.go
@@ -0,0 +1,256 @@
+package devicedetection
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFromHeaders(t *testing.T) {
+	extractor := newEvidenceExtractor()
+
+	req := http.Request{
+		Header: make(map[string][]string),
+	}
+	req.Header.Add("header", "Value")
+	req.Header.Add("Sec-CH-UA-Full-Version-List", "Chrome;12")
+	evidenceKeys := []dd.EvidenceKey{
+		{
+			Prefix: dd.EvidencePrefix(10),
+			Key:    "header",
+		},
+		{
+			Prefix: dd.EvidencePrefix(10),
+			Key:    "Sec-CH-UA-Full-Version-List",
+		},
+	}
+
+	evidence := extractor.fromHeaders(&req, evidenceKeys)
+
+	assert.NotNil(t, evidence)
+	assert.NotEmpty(t, evidence)
+	assert.Equal(t, evidence[0].Value, "Value")
+	assert.Equal(t, evidence[0].Key, "header")
+	assert.Equal(t, evidence[1].Value, "Chrome;12")
+	assert.Equal(t, evidence[1].Key, "Sec-CH-UA-Full-Version-List")
+}
+
+func TestFromSuaPayload(t *testing.T) {
+	tests := []struct {
+		name             string
+		payload          []byte
+		evidenceSize     int
+		evidenceKeyOrder int
+		expectedKey      string
+		expectedValue    string
+	}{
+		{
+			name: "from_SUA_tag",
+			payload: []byte(`{
+				"device": {
+					"sua": {
+						"browsers": [
+							{
+								"brand": "Google Chrome",
+								"version": ["121", "0", "6167", "184"]
+							}
+						],
+						"platform": {
+							"brand": "macOS",
+							"version": ["14", "0", "0"]
+						},
+						"architecture": "arm"
+					}
+				}
+			}`),
+			evidenceSize:     4,
+			evidenceKeyOrder: 0,
+			expectedKey:      "Sec-Ch-Ua-Arch",
+			expectedValue:    "arm",
+		},
+		{
+			name: "from_UA_headers",
+			payload: []byte(`{
+				"device": {
+					"ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
+					"sua": {
+						"architecture": "arm"
+					}
+				}
+			}`),
+			evidenceSize:     2,
+			evidenceKeyOrder: 1,
+			expectedKey:      "Sec-Ch-Ua-Arch",
+			expectedValue:    "arm",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			extractor := newEvidenceExtractor()
+
+			evidence := extractor.fromSuaPayload(tt.payload)
+
+			assert.NotNil(t, evidence)
+			assert.NotEmpty(t, evidence)
+			assert.Equal(t, len(evidence), tt.evidenceSize)
+			assert.Equal(t, evidence[tt.evidenceKeyOrder].Key, tt.expectedKey)
+			assert.Equal(t, evidence[tt.evidenceKeyOrder].Value, tt.expectedValue)
+		})
+	}
+}
+
+func TestExtract(t *testing.T) {
+	uaEvidence1 := stringEvidence{
+		Prefix: "ua1",
+		Key:    userAgentHeader,
+		Value:  "uav1",
+	}
+	uaEvidence2 := stringEvidence{
+		Prefix: "ua2",
+		Key:    userAgentHeader,
+		Value:  "uav2",
+	}
+	evidence1 := stringEvidence{
+		Prefix: "e1",
+		Key:    "k1",
+		Value:  "v1",
+	}
+	emptyEvidence := stringEvidence{
+		Prefix: "empty",
+		Key:    "e1",
+		Value:  "",
+	}
+
+	tests := []struct {
+		name              string
+		ctx               hookstage.ModuleContext
+		wantEvidenceCount int
+		wantUserAgent     string
+		wantError         bool
+	}{
+		{
+			name:      "nil",
+			ctx:       nil,
+			wantError: true,
+		},
+		{
+			name: "empty",
+			ctx: hookstage.ModuleContext{
+				evidenceFromSuaCtxKey:     []stringEvidence{},
+				evidenceFromHeadersCtxKey: []stringEvidence{},
+			},
+			wantEvidenceCount: 0,
+			wantUserAgent:     "",
+		},
+		{
+			name: "from_headers",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1},
+			},
+			wantEvidenceCount: 1,
+			wantUserAgent:     "uav1",
+		},
+		{
+			name: "from_headers_no_user_agent",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{evidence1},
+			},
+			wantError: true,
+		},
+		{
+			name: "from_sua",
+			ctx: hookstage.ModuleContext{
+				evidenceFromSuaCtxKey: []stringEvidence{uaEvidence1},
+			},
+			wantEvidenceCount: 1,
+			wantUserAgent:     "uav1",
+		},
+		{
+			name: "from_sua_no_user_agent",
+			ctx: hookstage.ModuleContext{
+				evidenceFromSuaCtxKey: []stringEvidence{evidence1},
+			},
+			wantError: true,
+		},
+		{
+			name: "from_headers_error",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: "bad value",
+			},
+			wantError: true,
+		},
+		{
+			name: "from_sua_error",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{},
+				evidenceFromSuaCtxKey:     "bad value",
+			},
+			wantError: true,
+		},
+		{
+			name: "from_sua_and_headers",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1},
+				evidenceFromSuaCtxKey:     []stringEvidence{evidence1},
+			},
+			wantEvidenceCount: 2,
+			wantUserAgent:     "uav1",
+		},
+		{
+			name: "from_sua_and_headers_sua_can_overwrite_if_ua_present",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1},
+				evidenceFromSuaCtxKey:     []stringEvidence{uaEvidence2},
+			},
+			wantEvidenceCount: 1,
+			wantUserAgent:     "uav2",
+		},
+		{
+			name: "empty_string_values",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{emptyEvidence},
+			},
+			wantError: true,
+		},
+		{
+			name: "empty_sua_values",
+			ctx: hookstage.ModuleContext{
+				evidenceFromSuaCtxKey: []stringEvidence{emptyEvidence},
+			},
+			wantError: true,
+		},
+		{
+			name: "mixed_valid_and_invalid",
+			ctx: hookstage.ModuleContext{
+				evidenceFromHeadersCtxKey: []stringEvidence{uaEvidence1},
+				evidenceFromSuaCtxKey:     "bad value",
+			},
+			wantError: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			extractor := newEvidenceExtractor()
+			evidence, userAgent, err := extractor.extract(test.ctx)
+
+			if test.wantError {
+				assert.Error(t, err)
+				assert.Nil(t, evidence)
+				assert.Equal(t, userAgent, "")
+			} else if test.wantEvidenceCount == 0 {
+				assert.NoError(t, err)
+				assert.Nil(t, evidence)
+				assert.Equal(t, userAgent, "")
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, len(evidence), test.wantEvidenceCount)
+				assert.Equal(t, userAgent, test.wantUserAgent)
+			}
+		})
+	}
+}
diff --git a/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go
new file mode 100644
index 00000000000..7237698117d
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types.go
@@ -0,0 +1,77 @@
+package devicedetection
+
+import (
+	"github.com/prebid/openrtb/v20/adcom1"
+)
+
+type deviceTypeMap = map[deviceType]adcom1.DeviceType
+
+var mobileOrTabletDeviceTypes = []deviceType{
+	deviceTypeMobile,
+	deviceTypeSmartPhone,
+}
+
+var personalComputerDeviceTypes = []deviceType{
+	deviceTypeDesktop,
+	deviceTypeEReader,
+	deviceTypeVehicleDisplay,
+}
+
+var tvDeviceTypes = []deviceType{
+	deviceTypeTv,
+}
+
+var phoneDeviceTypes = []deviceType{
+	deviceTypePhone,
+}
+
+var tabletDeviceTypes = []deviceType{
+	deviceTypeTablet,
+}
+
+var connectedDeviceTypes = []deviceType{
+	deviceTypeIoT,
+	deviceTypeRouter,
+	deviceTypeSmallScreen,
+	deviceTypeSmartSpeaker,
+	deviceTypeSmartWatch,
+}
+
+var setTopBoxDeviceTypes = []deviceType{
+	deviceTypeMediaHub,
+	deviceTypeConsole,
+}
+
+var oohDeviceTypes = []deviceType{
+	deviceTypeKiosk,
+}
+
+func applyCollection(items []deviceType, value adcom1.DeviceType, mappedCollection deviceTypeMap) {
+	for _, item := range items {
+		mappedCollection[item] = value
+	}
+}
+
+var deviceTypeMapCollection = deviceTypeMap{}
+
+func init() {
+	applyCollection(mobileOrTabletDeviceTypes, adcom1.DeviceMobile, deviceTypeMapCollection)
+	applyCollection(personalComputerDeviceTypes, adcom1.DevicePC, deviceTypeMapCollection)
+	applyCollection(tvDeviceTypes, adcom1.DeviceTV, deviceTypeMapCollection)
+	applyCollection(phoneDeviceTypes, adcom1.DevicePhone, deviceTypeMapCollection)
+	applyCollection(tabletDeviceTypes, adcom1.DeviceTablet, deviceTypeMapCollection)
+	applyCollection(connectedDeviceTypes, adcom1.DeviceConnected, deviceTypeMapCollection)
+	applyCollection(setTopBoxDeviceTypes, adcom1.DeviceSetTopBox, deviceTypeMapCollection)
+	applyCollection(oohDeviceTypes, adcom1.DeviceOOH, deviceTypeMapCollection)
+}
+
+// fiftyOneDtToRTB converts a 51Degrees device type to an OpenRTB device type.
+// If the device type is not recognized, it defaults to PC.
+func fiftyOneDtToRTB(val string) adcom1.DeviceType {
+	id, ok := deviceTypeMapCollection[deviceType(val)]
+	if ok {
+		return id
+	}
+
+	return adcom1.DevicePC
+}
diff --git a/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go
new file mode 100644
index 00000000000..5fd0203bac8
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/fiftyone_device_types_test.go
@@ -0,0 +1,90 @@
+package devicedetection
+
+import (
+	"testing"
+
+	"github.com/prebid/openrtb/v20/adcom1"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFiftyOneDtToRTB(t *testing.T) {
+	cases := []struct {
+		fiftyOneDt string
+		rtbDt      adcom1.DeviceType
+	}{
+		{
+			fiftyOneDt: "Phone",
+			rtbDt:      adcom1.DevicePhone,
+		},
+		{
+			fiftyOneDt: "Console",
+			rtbDt:      adcom1.DeviceSetTopBox,
+		},
+		{
+			fiftyOneDt: "Desktop",
+			rtbDt:      adcom1.DevicePC,
+		},
+		{
+			fiftyOneDt: "EReader",
+			rtbDt:      adcom1.DevicePC,
+		},
+		{
+			fiftyOneDt: "IoT",
+			rtbDt:      adcom1.DeviceConnected,
+		},
+		{
+			fiftyOneDt: "Kiosk",
+			rtbDt:      adcom1.DeviceOOH,
+		},
+		{
+			fiftyOneDt: "MediaHub",
+			rtbDt:      adcom1.DeviceSetTopBox,
+		},
+		{
+			fiftyOneDt: "Mobile",
+			rtbDt:      adcom1.DeviceMobile,
+		},
+		{
+			fiftyOneDt: "Router",
+			rtbDt:      adcom1.DeviceConnected,
+		},
+		{
+			fiftyOneDt: "SmallScreen",
+			rtbDt:      adcom1.DeviceConnected,
+		},
+		{
+			fiftyOneDt: "SmartPhone",
+			rtbDt:      adcom1.DeviceMobile,
+		},
+		{
+			fiftyOneDt: "SmartSpeaker",
+			rtbDt:      adcom1.DeviceConnected,
+		},
+		{
+			fiftyOneDt: "SmartWatch",
+			rtbDt:      adcom1.DeviceConnected,
+		},
+		{
+			fiftyOneDt: "Tablet",
+			rtbDt:      adcom1.DeviceTablet,
+		},
+		{
+			fiftyOneDt: "Tv",
+			rtbDt:      adcom1.DeviceTV,
+		},
+		{
+			fiftyOneDt: "Vehicle Display",
+			rtbDt:      adcom1.DevicePC,
+		},
+		{
+			fiftyOneDt: "Unknown",
+			rtbDt:      adcom1.DevicePC,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.fiftyOneDt, func(t *testing.T) {
+			assert.Equal(t, c.rtbDt, fiftyOneDtToRTB(c.fiftyOneDt))
+		})
+	}
+}
diff --git a/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go
new file mode 100644
index 00000000000..911f20e1840
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/hook_auction_entrypoint.go
@@ -0,0 +1,27 @@
+package devicedetection
+
+import (
+	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+)
+
+// handleAuctionEntryPointRequestHook is a hookstage.HookFunc that is used to handle the auction entrypoint request hook.
+func handleAuctionEntryPointRequestHook(cfg config, payload hookstage.EntrypointPayload, deviceDetector deviceDetector, evidenceExtractor evidenceExtractor, accountValidator accountValidator) (result hookstage.HookResult[hookstage.EntrypointPayload], err error) {
+	// if account/domain is not allowed, return failure
+	if !accountValidator.isAllowed(cfg, payload.Body) {
+		return hookstage.HookResult[hookstage.EntrypointPayload]{}, hookexecution.NewFailure("account not allowed")
+	}
+	// fetch evidence from headers and sua
+	evidenceFromHeaders := evidenceExtractor.fromHeaders(payload.Request, deviceDetector.getSupportedHeaders())
+	evidenceFromSua := evidenceExtractor.fromSuaPayload(payload.Body)
+
+	// create a Module context and set the evidence from headers, evidence from sua and dd enabled flag
+	moduleContext := make(hookstage.ModuleContext)
+	moduleContext[evidenceFromHeadersCtxKey] = evidenceFromHeaders
+	moduleContext[evidenceFromSuaCtxKey] = evidenceFromSua
+	moduleContext[ddEnabledCtxKey] = true
+
+	return hookstage.HookResult[hookstage.EntrypointPayload]{
+		ModuleContext: moduleContext,
+	}, nil
+}
diff --git a/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go b/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go
new file mode 100644
index 00000000000..1146c3cc639
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/hook_raw_auction_request.go
@@ -0,0 +1,173 @@
+package devicedetection
+
+import (
+	"fmt"
+	"math"
+
+	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+	"github.com/tidwall/gjson"
+	"github.com/tidwall/sjson"
+)
+
+func handleAuctionRequestHook(ctx hookstage.ModuleInvocationContext, deviceDetector deviceDetector, evidenceExtractor evidenceExtractor) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
+	var result hookstage.HookResult[hookstage.RawAuctionRequestPayload]
+
+	// If the entrypoint hook was not configured, return the result without any changes
+	if ctx.ModuleContext == nil {
+		return result, hookexecution.NewFailure("entrypoint hook was not configured")
+	}
+
+	result.ChangeSet.AddMutation(
+		func(rawPayload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) {
+			evidence, ua, err := evidenceExtractor.extract(ctx.ModuleContext)
+			if err != nil {
+				return rawPayload, hookexecution.NewFailure("error extracting evidence %s", err)
+			}
+			if evidence == nil {
+				return rawPayload, hookexecution.NewFailure("error extracting evidence")
+			}
+
+			deviceInfo, err := deviceDetector.getDeviceInfo(evidence, ua)
+			if err != nil {
+				return rawPayload, hookexecution.NewFailure("error getting device info %s", err)
+			}
+
+			result, err := hydrateFields(deviceInfo, rawPayload)
+			if err != nil {
+				return rawPayload, hookexecution.NewFailure(fmt.Sprintf("error hydrating fields %s", err))
+			}
+
+			return result, nil
+		}, hookstage.MutationUpdate,
+	)
+
+	return result, nil
+}
+
+// hydrateFields hydrates the fields in the raw auction request payload with the device information
+func hydrateFields(fiftyOneDd *deviceInfo, payload hookstage.RawAuctionRequestPayload) (hookstage.RawAuctionRequestPayload, error) {
+	devicePayload := gjson.GetBytes(payload, "device")
+	dPV := devicePayload.Value()
+	if dPV == nil {
+		return payload, nil
+	}
+
+	deviceObject := dPV.(map[string]any)
+	deviceObject = setMissingFields(deviceObject, fiftyOneDd)
+	deviceObject = signDeviceData(deviceObject, fiftyOneDd)
+
+	return mergeDeviceIntoPayload(payload, deviceObject)
+}
+
+// setMissingFields sets fields such as ["devicetype", "ua", "make", "os", "osv", "h", "w", "pxratio", "js", "geoFetch", "model", "ppi"]
+// if they are not already present in the device object
+func setMissingFields(deviceObj map[string]any, fiftyOneDd *deviceInfo) map[string]any {
+	optionalFields := map[string]func() any{
+		"devicetype": func() any {
+			return fiftyOneDtToRTB(fiftyOneDd.DeviceType)
+		},
+		"ua": func() any {
+			if fiftyOneDd.UserAgent != ddUnknown {
+				return fiftyOneDd.UserAgent
+			}
+			return nil
+		},
+		"make": func() any {
+			if fiftyOneDd.HardwareVendor != ddUnknown {
+				return fiftyOneDd.HardwareVendor
+			}
+			return nil
+		},
+		"os": func() any {
+			if fiftyOneDd.PlatformName != ddUnknown {
+				return fiftyOneDd.PlatformName
+			}
+			return nil
+		},
+		"osv": func() any {
+			if fiftyOneDd.PlatformVersion != ddUnknown {
+				return fiftyOneDd.PlatformVersion
+			}
+			return nil
+		},
+		"h": func() any {
+			return fiftyOneDd.ScreenPixelsHeight
+		},
+		"w": func() any {
+			return fiftyOneDd.ScreenPixelsWidth
+		},
+		"pxratio": func() any {
+			return fiftyOneDd.PixelRatio
+		},
+		"js": func() any {
+			val := 0
+			if fiftyOneDd.Javascript {
+				val = 1
+			}
+			return val
+		},
+		"geoFetch": func() any {
+			val := 0
+			if fiftyOneDd.GeoLocation {
+				val = 1
+			}
+			return val
+		},
+		"model": func() any {
+			newVal := fiftyOneDd.HardwareModel
+			if newVal == ddUnknown {
+				newVal = fiftyOneDd.HardwareName
+			}
+			if newVal != ddUnknown {
+				return newVal
+			}
+			return nil
+		},
+		"ppi": func() any {
+			if fiftyOneDd.ScreenPixelsHeight > 0 && fiftyOneDd.ScreenInchesHeight > 0 {
+				ppi := float64(fiftyOneDd.ScreenPixelsHeight) / fiftyOneDd.ScreenInchesHeight
+				return int(math.Round(ppi))
+			}
+			return nil
+		},
+	}
+
+	for field, valFunc := range optionalFields {
+		_, ok := deviceObj[field]
+		if !ok {
+			val := valFunc()
+			if val != nil {
+				deviceObj[field] = val
+			}
+		}
+	}
+
+	return deviceObj
+}
+
+// signDeviceData signs the device data with the device information in the ext map of the device object
+func signDeviceData(deviceObj map[string]any, fiftyOneDd *deviceInfo) map[string]any {
+	extObj, ok := deviceObj["ext"]
+	var ext map[string]any
+	if ok {
+		ext = extObj.(map[string]any)
+	} else {
+		ext = make(map[string]any)
+	}
+
+	ext["fiftyonedegrees_deviceId"] = fiftyOneDd.DeviceId
+	deviceObj["ext"] = ext
+
+	return deviceObj
+}
+
+// mergeDeviceIntoPayload merges the modified device object back into the RawAuctionRequestPayload
+func mergeDeviceIntoPayload(payload hookstage.RawAuctionRequestPayload, deviceObject map[string]any) (hookstage.RawAuctionRequestPayload, error) {
+	newPayload, err := sjson.SetBytes(payload, "device", deviceObject)
+	if err != nil {
+		return payload, err
+	}
+
+	return newPayload, nil
+}
diff --git a/modules/fiftyonedegrees/devicedetection/models.go b/modules/fiftyonedegrees/devicedetection/models.go
new file mode 100644
index 00000000000..c58daa211fd
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/models.go
@@ -0,0 +1,66 @@
+package devicedetection
+
+// Prefixes in literal format
+const queryPrefix = "query."
+const headerPrefix = "header."
+const ddUnknown = "Unknown"
+
+// Evidence where all fields are in string format
+type stringEvidence struct {
+	Prefix string
+	Key    string
+	Value  string
+}
+
+func getEvidenceByKey(e []stringEvidence, key string) (stringEvidence, bool) {
+	for _, evidence := range e {
+		if evidence.Key == key {
+			return evidence, true
+		}
+	}
+	return stringEvidence{}, false
+}
+
+type deviceType string
+
+const (
+	deviceTypePhone          = "Phone"
+	deviceTypeConsole        = "Console"
+	deviceTypeDesktop        = "Desktop"
+	deviceTypeEReader        = "EReader"
+	deviceTypeIoT            = "IoT"
+	deviceTypeKiosk          = "Kiosk"
+	deviceTypeMediaHub       = "MediaHub"
+	deviceTypeMobile         = "Mobile"
+	deviceTypeRouter         = "Router"
+	deviceTypeSmallScreen    = "SmallScreen"
+	deviceTypeSmartPhone     = "SmartPhone"
+	deviceTypeSmartSpeaker   = "SmartSpeaker"
+	deviceTypeSmartWatch     = "SmartWatch"
+	deviceTypeTablet         = "Tablet"
+	deviceTypeTv             = "Tv"
+	deviceTypeVehicleDisplay = "Vehicle Display"
+)
+
+type deviceInfo struct {
+	HardwareVendor        string
+	HardwareName          string
+	DeviceType            string
+	PlatformVendor        string
+	PlatformName          string
+	PlatformVersion       string
+	BrowserVendor         string
+	BrowserName           string
+	BrowserVersion        string
+	ScreenPixelsWidth     int64
+	ScreenPixelsHeight    int64
+	PixelRatio            float64
+	Javascript            bool
+	GeoLocation           bool
+	HardwareFamily        string
+	HardwareModel         string
+	HardwareModelVariants string
+	UserAgent             string
+	DeviceId              string
+	ScreenInchesHeight    float64
+}
diff --git a/modules/fiftyonedegrees/devicedetection/models_test.go b/modules/fiftyonedegrees/devicedetection/models_test.go
new file mode 100644
index 00000000000..898f25f4144
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/models_test.go
@@ -0,0 +1,63 @@
+package devicedetection
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetEvidenceByKey(t *testing.T) {
+	populatedEvidence := []stringEvidence{
+		{Key: "key1", Value: "value1"},
+		{Key: "key2", Value: "value2"},
+		{Key: "key3", Value: "value3"},
+	}
+
+	tests := []struct {
+		name           string
+		evidence       []stringEvidence
+		key            string
+		expectEvidence stringEvidence
+		expectFound    bool
+	}{
+		{
+			name:           "nil_evidence",
+			evidence:       nil,
+			key:            "key2",
+			expectEvidence: stringEvidence{},
+			expectFound:    false,
+		},
+		{
+			name:           "empty_evidence",
+			evidence:       []stringEvidence{},
+			key:            "key2",
+			expectEvidence: stringEvidence{},
+			expectFound:    false,
+		},
+		{
+			name:     "key_found",
+			evidence: populatedEvidence,
+			key:      "key2",
+			expectEvidence: stringEvidence{
+				Key:   "key2",
+				Value: "value2",
+			},
+			expectFound: true,
+		},
+		{
+			name:           "key_not_found",
+			evidence:       populatedEvidence,
+			key:            "key4",
+			expectEvidence: stringEvidence{},
+			expectFound:    false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result, exists := getEvidenceByKey(test.evidence, test.key)
+			assert.Equal(t, test.expectFound, exists)
+			assert.Equal(t, test.expectEvidence, result)
+		})
+	}
+}
diff --git a/modules/fiftyonedegrees/devicedetection/module.go b/modules/fiftyonedegrees/devicedetection/module.go
new file mode 100644
index 00000000000..df72e6338a5
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/module.go
@@ -0,0 +1,107 @@
+package devicedetection
+
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/51Degrees/device-detection-go/v4/onpremise"
+	"github.com/pkg/errors"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+	"github.com/prebid/prebid-server/v2/modules/moduledeps"
+)
+
+func configHashFromConfig(cfg *config) *dd.ConfigHash {
+	configHash := dd.NewConfigHash(cfg.getPerformanceProfile())
+	if cfg.Performance.Concurrency != nil {
+		configHash.SetConcurrency(uint16(*cfg.Performance.Concurrency))
+	}
+
+	if cfg.Performance.Difference != nil {
+		configHash.SetDifference(int32(*cfg.Performance.Difference))
+	}
+
+	if cfg.Performance.AllowUnmatched != nil {
+		configHash.SetAllowUnmatched(*cfg.Performance.AllowUnmatched)
+	}
+
+	if cfg.Performance.Drift != nil {
+		configHash.SetDrift(int32(*cfg.Performance.Drift))
+	}
+	return configHash
+}
+
+func Builder(rawConfig json.RawMessage, _ moduledeps.ModuleDeps) (interface{}, error) {
+	cfg, err := parseConfig(rawConfig)
+	if err != nil {
+		return Module{}, errors.Wrap(err, "failed to parse config")
+	}
+
+	err = validateConfig(cfg)
+	if err != nil {
+		return nil, errors.Wrap(err, "invalid config")
+	}
+
+	configHash := configHashFromConfig(&cfg)
+
+	deviceDetectorImpl, err := newDeviceDetector(
+		configHash,
+		&cfg,
+	)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create device detector")
+	}
+
+	return Module{
+			cfg,
+			deviceDetectorImpl,
+			newEvidenceExtractor(),
+			newAccountValidator(),
+		},
+		nil
+}
+
+type Module struct {
+	config            config
+	deviceDetector    deviceDetector
+	evidenceExtractor evidenceExtractor
+	accountValidator  accountValidator
+}
+
+type deviceDetector interface {
+	getSupportedHeaders() []dd.EvidenceKey
+	getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error)
+}
+
+type accountValidator interface {
+	isAllowed(cfg config, req []byte) bool
+}
+
+type evidenceExtractor interface {
+	fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence
+	fromSuaPayload(payload []byte) []stringEvidence
+	extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error)
+}
+
+func (m Module) HandleEntrypointHook(
+	_ context.Context,
+	_ hookstage.ModuleInvocationContext,
+	payload hookstage.EntrypointPayload,
+) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
+	return handleAuctionEntryPointRequestHook(
+		m.config,
+		payload,
+		m.deviceDetector,
+		m.evidenceExtractor,
+		m.accountValidator,
+	)
+}
+
+func (m Module) HandleRawAuctionHook(
+	_ context.Context,
+	mCtx hookstage.ModuleInvocationContext,
+	_ hookstage.RawAuctionRequestPayload,
+) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
+	return handleAuctionRequestHook(mCtx, m.deviceDetector, m.evidenceExtractor)
+}
diff --git a/modules/fiftyonedegrees/devicedetection/module_test.go b/modules/fiftyonedegrees/devicedetection/module_test.go
new file mode 100644
index 00000000000..7b8095ac431
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/module_test.go
@@ -0,0 +1,703 @@
+package devicedetection
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"os"
+	"testing"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/51Degrees/device-detection-go/v4/onpremise"
+	"github.com/prebid/prebid-server/v2/hooks/hookstage"
+	"github.com/prebid/prebid-server/v2/modules/moduledeps"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+type mockAccValidator struct {
+	mock.Mock
+}
+
+func (m *mockAccValidator) isAllowed(cfg config, req []byte) bool {
+	args := m.Called(cfg, req)
+	return args.Bool(0)
+}
+
+type mockEvidenceExtractor struct {
+	mock.Mock
+}
+
+func (m *mockEvidenceExtractor) fromHeaders(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence {
+	args := m.Called(request, httpHeaderKeys)
+
+	return args.Get(0).([]stringEvidence)
+}
+
+func (m *mockEvidenceExtractor) fromSuaPayload(payload []byte) []stringEvidence {
+	args := m.Called(payload)
+
+	return args.Get(0).([]stringEvidence)
+}
+
+func (m *mockEvidenceExtractor) extract(ctx hookstage.ModuleContext) ([]onpremise.Evidence, string, error) {
+	args := m.Called(ctx)
+
+	res := args.Get(0)
+	if res == nil {
+		return nil, args.String(1), args.Error(2)
+	}
+
+	return res.([]onpremise.Evidence), args.String(1), args.Error(2)
+}
+
+type mockDeviceDetector struct {
+	mock.Mock
+}
+
+func (m *mockDeviceDetector) getSupportedHeaders() []dd.EvidenceKey {
+	args := m.Called()
+	return args.Get(0).([]dd.EvidenceKey)
+}
+
+func (m *mockDeviceDetector) getDeviceInfo(evidence []onpremise.Evidence, ua string) (*deviceInfo, error) {
+
+	args := m.Called(evidence, ua)
+
+	res := args.Get(0)
+
+	if res == nil {
+		return nil, args.Error(1)
+	}
+
+	return res.(*deviceInfo), args.Error(1)
+}
+
+func TestHandleEntrypointHookAccountNotAllowed(t *testing.T) {
+	var mockValidator mockAccValidator
+
+	mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(false)
+
+	module := Module{
+		accountValidator: &mockValidator,
+	}
+
+	_, err := module.HandleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, hookstage.EntrypointPayload{})
+	assert.Error(t, err)
+	assert.Equal(t, "hook execution failed: account not allowed", err.Error())
+}
+
+func TestHandleEntrypointHookAccountAllowed(t *testing.T) {
+	var mockValidator mockAccValidator
+
+	mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true)
+
+	var mockEvidenceExtractor mockEvidenceExtractor
+	mockEvidenceExtractor.On("fromHeaders", mock.Anything, mock.Anything).Return(
+		[]stringEvidence{{
+			Prefix: "123",
+			Key:    "key",
+			Value:  "val",
+		}},
+	)
+
+	mockEvidenceExtractor.On("fromSuaPayload", mock.Anything, mock.Anything).Return(
+		[]stringEvidence{{
+			Prefix: "123",
+			Key:    "User-Agent",
+			Value:  "ua",
+		}},
+	)
+
+	var mockDeviceDetector mockDeviceDetector
+
+	mockDeviceDetector.On("getSupportedHeaders").Return(
+		[]dd.EvidenceKey{{
+			Prefix: dd.HttpEvidenceQuery,
+			Key:    "key",
+		}},
+	)
+
+	module := Module{
+		deviceDetector:    &mockDeviceDetector,
+		evidenceExtractor: &mockEvidenceExtractor,
+		accountValidator:  &mockValidator,
+	}
+
+	result, err := module.HandleEntrypointHook(nil, hookstage.ModuleInvocationContext{}, hookstage.EntrypointPayload{})
+	assert.NoError(t, err)
+
+	assert.Equal(
+		t, result.ModuleContext[evidenceFromHeadersCtxKey], []stringEvidence{{
+			Prefix: "123",
+			Key:    "key",
+			Value:  "val",
+		}},
+	)
+
+	assert.Equal(
+		t, result.ModuleContext[evidenceFromSuaCtxKey], []stringEvidence{{
+			Prefix: "123",
+			Key:    "User-Agent",
+			Value:  "ua",
+		}},
+	)
+}
+
+func TestHandleRawAuctionHookNoCtx(t *testing.T) {
+	module := Module{}
+
+	_, err := module.HandleRawAuctionHook(
+		nil,
+		hookstage.ModuleInvocationContext{},
+		hookstage.RawAuctionRequestPayload{},
+	)
+	assert.Errorf(t, err, "entrypoint hook was not configured")
+}
+
+func TestHandleRawAuctionHookExtractError(t *testing.T) {
+	var mockValidator mockAccValidator
+
+	mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true)
+
+	var evidenceExtractorM mockEvidenceExtractor
+	evidenceExtractorM.On("extract", mock.Anything).Return(
+		nil,
+		"ua",
+		nil,
+	)
+
+	var mockDeviceDetector mockDeviceDetector
+
+	module := Module{
+		deviceDetector:    &mockDeviceDetector,
+		evidenceExtractor: &evidenceExtractorM,
+		accountValidator:  &mockValidator,
+	}
+
+	mctx := make(hookstage.ModuleContext)
+
+	mctx[ddEnabledCtxKey] = true
+
+	result, err := module.HandleRawAuctionHook(
+		context.TODO(), hookstage.ModuleInvocationContext{
+			ModuleContext: mctx,
+		},
+		hookstage.RawAuctionRequestPayload{},
+	)
+
+	assert.NoError(t, err)
+	assert.Equal(t, len(result.ChangeSet.Mutations()), 1)
+	assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate)
+
+	mutation := result.ChangeSet.Mutations()[0]
+
+	body := []byte(`{}`)
+
+	_, err = mutation.Apply(body)
+	assert.Errorf(t, err, "error extracting evidence")
+
+	var mockEvidenceErrExtractor mockEvidenceExtractor
+	mockEvidenceErrExtractor.On("extract", mock.Anything).Return(
+		nil,
+		"",
+		errors.New("error"),
+	)
+
+	module.evidenceExtractor = &mockEvidenceErrExtractor
+
+	result, err = module.HandleRawAuctionHook(
+		context.TODO(), hookstage.ModuleInvocationContext{
+			ModuleContext: mctx,
+		},
+		hookstage.RawAuctionRequestPayload{},
+	)
+
+	assert.NoError(t, err)
+
+	assert.Equal(t, len(result.ChangeSet.Mutations()), 1)
+
+	assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate)
+
+	mutation = result.ChangeSet.Mutations()[0]
+
+	_, err = mutation.Apply(body)
+	assert.Errorf(t, err, "error extracting evidence error")
+
+}
+
+func TestHandleRawAuctionHookEnrichment(t *testing.T) {
+	var mockValidator mockAccValidator
+
+	mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true)
+
+	var mockEvidenceExtractor mockEvidenceExtractor
+	mockEvidenceExtractor.On("extract", mock.Anything).Return(
+		[]onpremise.Evidence{
+			{
+				Key:   "key",
+				Value: "val",
+			},
+		},
+		"ua",
+		nil,
+	)
+
+	var deviceDetectorM mockDeviceDetector
+
+	deviceDetectorM.On("getDeviceInfo", mock.Anything, mock.Anything).Return(
+		&deviceInfo{
+			HardwareVendor:        "Apple",
+			HardwareName:          "Macbook",
+			DeviceType:            "device",
+			PlatformVendor:        "Apple",
+			PlatformName:          "MacOs",
+			PlatformVersion:       "14",
+			BrowserVendor:         "Google",
+			BrowserName:           "Crome",
+			BrowserVersion:        "12",
+			ScreenPixelsWidth:     1024,
+			ScreenPixelsHeight:    1080,
+			PixelRatio:            223,
+			Javascript:            true,
+			GeoLocation:           true,
+			HardwareFamily:        "Macbook",
+			HardwareModel:         "Macbook",
+			HardwareModelVariants: "Macbook",
+			UserAgent:             "ua",
+			DeviceId:              "",
+		},
+		nil,
+	)
+
+	module := Module{
+		deviceDetector:    &deviceDetectorM,
+		evidenceExtractor: &mockEvidenceExtractor,
+		accountValidator:  &mockValidator,
+	}
+
+	mctx := make(hookstage.ModuleContext)
+	mctx[ddEnabledCtxKey] = true
+
+	result, err := module.HandleRawAuctionHook(
+		nil, hookstage.ModuleInvocationContext{
+			ModuleContext: mctx,
+		},
+		[]byte{},
+	)
+	assert.NoError(t, err)
+	assert.Equal(t, len(result.ChangeSet.Mutations()), 1)
+	assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate)
+
+	mutation := result.ChangeSet.Mutations()[0]
+
+	body := []byte(`{
+		"device": {
+			"connectiontype": 2,
+			"ext": {
+				"atts": 0,
+				"ifv": "1B8EFA09-FF8F-4123-B07F-7283B50B3870"
+			},
+			"sua": {
+				"source": 2,
+				"browsers": [
+					{
+						"brand": "Not A(Brand",
+						"version": [
+							"99",
+							"0",
+							"0",
+							"0"
+						]
+					},
+					{
+						"brand": "Google Chrome",
+						"version": [
+							"121",
+							"0",
+							"6167",
+							"184"
+						]
+					},
+					{
+						"brand": "Chromium",
+						"version": [
+							"121",
+							"0",
+							"6167",
+							"184"
+						]
+					}
+				],
+				"platform": {
+					"brand": "macOS",
+					"version": [
+						"14",
+						"0",
+						"0"
+					]
+				},
+				"mobile": 0,
+				"architecture": "arm",
+				"model": ""
+			}
+		}
+	}`)
+
+	mutationResult, err := mutation.Apply(body)
+
+	require.JSONEq(t, string(mutationResult), `{
+		"device": {
+			"connectiontype": 2,
+			"ext": {
+				"atts": 0,
+				"ifv": "1B8EFA09-FF8F-4123-B07F-7283B50B3870",
+				"fiftyonedegrees_deviceId":""
+			},
+			"sua": {
+				"source": 2,
+				"browsers": [
+					{
+						"brand": "Not A(Brand",
+						"version": [
+							"99",
+							"0",
+							"0",
+							"0"
+						]
+					},
+					{
+						"brand": "Google Chrome",
+						"version": [
+							"121",
+							"0",
+							"6167",
+							"184"
+						]
+					},
+					{
+						"brand": "Chromium",
+						"version": [
+							"121",
+							"0",
+							"6167",
+							"184"
+						]
+					}
+				],
+				"platform": {
+					"brand": "macOS",
+					"version": [
+						"14",
+						"0",
+						"0"
+					]
+				},
+				"mobile": 0,
+				"architecture": "arm",
+				"model": ""
+			}
+		,"devicetype":2,"ua":"ua","make":"Apple","model":"Macbook","os":"MacOs","osv":"14","h":1080,"w":1024,"pxratio":223,"js":1,"geoFetch":1}
+	}`)
+
+	var deviceDetectorErrM mockDeviceDetector
+
+	deviceDetectorErrM.On("getDeviceInfo", mock.Anything, mock.Anything).Return(
+		nil,
+		errors.New("error"),
+	)
+
+	module.deviceDetector = &deviceDetectorErrM
+
+	result, err = module.HandleRawAuctionHook(
+		nil, hookstage.ModuleInvocationContext{
+			ModuleContext: mctx,
+		},
+		[]byte{},
+	)
+
+	assert.NoError(t, err)
+
+	assert.Equal(t, len(result.ChangeSet.Mutations()), 1)
+
+	assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate)
+
+	mutation = result.ChangeSet.Mutations()[0]
+
+	_, err = mutation.Apply(body)
+	assert.Errorf(t, err, "error getting device info")
+}
+
+func TestHandleRawAuctionHookEnrichmentWithErrors(t *testing.T) {
+	var mockValidator mockAccValidator
+
+	mockValidator.On("isAllowed", mock.Anything, mock.Anything).Return(true)
+
+	var mockEvidenceExtractor mockEvidenceExtractor
+	mockEvidenceExtractor.On("extract", mock.Anything).Return(
+		[]onpremise.Evidence{
+			{
+				Key:   "key",
+				Value: "val",
+			},
+		},
+		"ua",
+		nil,
+	)
+
+	var mockDeviceDetector mockDeviceDetector
+
+	mockDeviceDetector.On("getDeviceInfo", mock.Anything, mock.Anything).Return(
+		&deviceInfo{
+			HardwareVendor:        "Apple",
+			HardwareName:          "Macbook",
+			DeviceType:            "device",
+			PlatformVendor:        "Apple",
+			PlatformName:          "MacOs",
+			PlatformVersion:       "14",
+			BrowserVendor:         "Google",
+			BrowserName:           "Crome",
+			BrowserVersion:        "12",
+			ScreenPixelsWidth:     1024,
+			ScreenPixelsHeight:    1080,
+			PixelRatio:            223,
+			Javascript:            true,
+			GeoLocation:           true,
+			HardwareFamily:        "Macbook",
+			HardwareModel:         "Macbook",
+			HardwareModelVariants: "Macbook",
+			UserAgent:             "ua",
+			DeviceId:              "",
+			ScreenInchesHeight:    7,
+		},
+		nil,
+	)
+
+	module := Module{
+		deviceDetector:    &mockDeviceDetector,
+		evidenceExtractor: &mockEvidenceExtractor,
+		accountValidator:  &mockValidator,
+	}
+
+	mctx := make(hookstage.ModuleContext)
+	mctx[ddEnabledCtxKey] = true
+
+	result, err := module.HandleRawAuctionHook(
+		nil, hookstage.ModuleInvocationContext{
+			ModuleContext: mctx,
+		},
+		[]byte{},
+	)
+	assert.NoError(t, err)
+	assert.Equal(t, len(result.ChangeSet.Mutations()), 1)
+	assert.Equal(t, result.ChangeSet.Mutations()[0].Type(), hookstage.MutationUpdate)
+
+	mutation := result.ChangeSet.Mutations()[0]
+
+	mutationResult, err := mutation.Apply(hookstage.RawAuctionRequestPayload(`{"device":{}}`))
+	assert.NoError(t, err)
+	require.JSONEq(t, string(mutationResult), `{"device":{"devicetype":2,"ua":"ua","make":"Apple","model":"Macbook","os":"MacOs","osv":"14","h":1080,"w":1024,"pxratio":223,"js":1,"geoFetch":1,"ppi":154,"ext":{"fiftyonedegrees_deviceId":""}}}`)
+}
+
+func TestConfigHashFromConfig(t *testing.T) {
+	cfg := config{
+		Performance: performance{
+			Profile:        "",
+			Concurrency:    nil,
+			Difference:     nil,
+			AllowUnmatched: nil,
+			Drift:          nil,
+		},
+	}
+
+	result := configHashFromConfig(&cfg)
+	assert.Equal(t, result.PerformanceProfile(), dd.Default)
+	assert.Equal(t, result.Concurrency(), uint16(0xa))
+	assert.Equal(t, result.Difference(), int32(0))
+	assert.Equal(t, result.AllowUnmatched(), false)
+	assert.Equal(t, result.Drift(), int32(0))
+
+	concurrency := 1
+	difference := 1
+	allowUnmatched := true
+	drift := 1
+
+	cfg = config{
+		Performance: performance{
+			Profile:        "Balanced",
+			Concurrency:    &concurrency,
+			Difference:     &difference,
+			AllowUnmatched: &allowUnmatched,
+			Drift:          &drift,
+		},
+	}
+
+	result = configHashFromConfig(&cfg)
+	assert.Equal(t, result.PerformanceProfile(), dd.Balanced)
+	assert.Equal(t, result.Concurrency(), uint16(1))
+	assert.Equal(t, result.Difference(), int32(1))
+	assert.Equal(t, result.AllowUnmatched(), true)
+	assert.Equal(t, result.Drift(), int32(1))
+
+	cfg = config{
+		Performance: performance{
+			Profile: "InMemory",
+		},
+	}
+	result = configHashFromConfig(&cfg)
+	assert.Equal(t, result.PerformanceProfile(), dd.InMemory)
+
+	cfg = config{
+		Performance: performance{
+			Profile: "HighPerformance",
+		},
+	}
+	result = configHashFromConfig(&cfg)
+	assert.Equal(t, result.PerformanceProfile(), dd.HighPerformance)
+}
+
+func TestSignDeviceData(t *testing.T) {
+	devicePld := map[string]any{
+		"ext": map[string]any{
+			"my-key": "my-value",
+		},
+	}
+
+	deviceInfo := deviceInfo{
+		DeviceId: "test-device-id",
+	}
+
+	result := signDeviceData(devicePld, &deviceInfo)
+	r, err := json.Marshal(result)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err.Error())
+	}
+
+	require.JSONEq(
+		t,
+		`{"ext":{"fiftyonedegrees_deviceId":"test-device-id","my-key":"my-value"}}`,
+		string(r),
+	)
+}
+
+func TestBuilderWithInvalidJson(t *testing.T) {
+	_, err := Builder([]byte(`{`), moduledeps.ModuleDeps{})
+	assert.Error(t, err)
+	assert.Errorf(t, err, "failed to parse config")
+}
+
+func TestBuilderWithInvalidConfig(t *testing.T) {
+	_, err := Builder([]byte(`{"data_file":{}}`), moduledeps.ModuleDeps{})
+	assert.Error(t, err)
+	assert.Errorf(t, err, "invalid config")
+}
+
+func TestBuilderHandleDeviceDetectorError(t *testing.T) {
+	var mockConfig config
+	mockConfig.Performance.Profile = "default"
+	testFile, _ := os.Create("test-builder-config.hash")
+	defer testFile.Close()
+	defer os.Remove("test-builder-config.hash")
+
+	_, err := Builder(
+		[]byte(`{
+			"enabled": true,
+			"data_file": {
+				"path": "test-builder-config.hash",
+				"update": {
+					"auto": true,
+					"url": "https://my.datafile.com/datafile.gz",
+					"polling_interval": 3600,
+					"licence_key": "your_licence_key",
+					"product": "V4Enterprise"
+            	}
+          	},
+			"account_filter": {"allow_list": ["123"]},
+			"performance": {
+				"profile": "123",
+				"concurrency": 1,
+				"difference": 1,
+				"allow_unmatched": true,
+				"drift": 1	
+			}
+		}`), moduledeps.ModuleDeps{},
+	)
+	assert.Error(t, err)
+	assert.Errorf(t, err, "failed to create device detector")
+}
+
+func TestHydrateFields(t *testing.T) {
+	deviceInfo := &deviceInfo{
+		HardwareVendor:        "Apple",
+		HardwareName:          "Macbook",
+		DeviceType:            "device",
+		PlatformVendor:        "Apple",
+		PlatformName:          "MacOs",
+		PlatformVersion:       "14",
+		BrowserVendor:         "Google",
+		BrowserName:           "Crome",
+		BrowserVersion:        "12",
+		ScreenPixelsWidth:     1024,
+		ScreenPixelsHeight:    1080,
+		PixelRatio:            223,
+		Javascript:            true,
+		GeoLocation:           true,
+		HardwareFamily:        "Macbook",
+		HardwareModel:         "Macbook",
+		HardwareModelVariants: "Macbook",
+		UserAgent:             "ua",
+		DeviceId:              "dev-ide",
+	}
+
+	rawPld := `{
+		"imp": [{
+			"id": "",
+			"banner": {
+				"topframe": 1,
+				"format": [
+					{
+						"w": 728,
+						"h": 90
+					}
+				],
+				"pos": 1
+			},
+			"bidfloor": 0.01,
+			"bidfloorcur": "USD"
+		}],
+		"device": {
+			"model": "Macintosh",
+			"w": 843,
+			"h": 901,
+			"dnt": 0,
+			"ua": "Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A037U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36",
+			"language": "en",
+			"sua": {"browsers":[{"brand":"Not/A)Brand","version":["99","0","0","0"]},{"brand":"Samsung Internet","version":["23","0","1","1"]},{"brand":"Chromium","version":["115","0","5790","168"]}],"platform":{"brand":"Android","version":["13","0","0"]},"mobile":1,"model":"SM-A037U","source":2},
+			"ext": {"h":"901","w":843}
+		},
+		"cur": [
+			"USD"
+		],
+		"tmax": 1700
+	}`
+
+	payload, err := hydrateFields(deviceInfo, []byte(rawPld))
+	assert.NoError(t, err)
+
+	var deviceHolder struct {
+		Device json.RawMessage `json:"device"`
+	}
+
+	err = json.Unmarshal(payload, &deviceHolder)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err.Error())
+	}
+
+	require.JSONEq(
+		t,
+		`{"devicetype":2,"dnt":0,"ext":{"fiftyonedegrees_deviceId":"dev-ide","h":"901","w":843},"geoFetch":1,"h":901,"js":1,"language":"en","make":"Apple","model":"Macintosh","os":"MacOs","osv":"14","pxratio":223,"sua":{"browsers":[{"brand":"Not/A)Brand","version":["99","0","0","0"]},{"brand":"Samsung Internet","version":["23","0","1","1"]},{"brand":"Chromium","version":["115","0","5790","168"]}],"mobile":1,"model":"SM-A037U","platform":{"brand":"Android","version":["13","0","0"]},"source":2},"ua":"Mozilla/5.0 (Linux; Android 13; SAMSUNG SM-A037U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/23.0 Chrome/115.0.0.0 Mobile Safari/537.36","w":843}`,
+		string(deviceHolder.Device),
+	)
+}
diff --git a/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go b/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go
new file mode 100644
index 00000000000..8440886b353
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/request_headers_extractor.go
@@ -0,0 +1,47 @@
+package devicedetection
+
+import (
+	"net/http"
+	"strings"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+)
+
+// evidenceFromRequestHeadersExtractor is a struct that extracts evidence from http request headers
+type evidenceFromRequestHeadersExtractor struct{}
+
+func newEvidenceFromRequestHeadersExtractor() evidenceFromRequestHeadersExtractor {
+	return evidenceFromRequestHeadersExtractor{}
+}
+
+func (x evidenceFromRequestHeadersExtractor) extract(request *http.Request, httpHeaderKeys []dd.EvidenceKey) []stringEvidence {
+	return x.extractEvidenceStrings(request, httpHeaderKeys)
+}
+
+func (x evidenceFromRequestHeadersExtractor) extractEvidenceStrings(r *http.Request, keys []dd.EvidenceKey) []stringEvidence {
+	evidence := make([]stringEvidence, 0)
+	for _, e := range keys {
+		if e.Prefix == dd.HttpEvidenceQuery {
+			continue
+		}
+
+		// Get evidence from headers
+		headerVal := r.Header.Get(e.Key)
+		if headerVal == "" {
+			continue
+		}
+
+		if e.Key != secUaFullVersionList && e.Key != secChUa {
+			headerVal = strings.Replace(headerVal, "\"", "", -1)
+		}
+
+		if headerVal != "" {
+			evidence = append(evidence, stringEvidence{
+				Prefix: headerPrefix,
+				Key:    e.Key,
+				Value:  headerVal,
+			})
+		}
+	}
+	return evidence
+}
diff --git a/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go b/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go
new file mode 100644
index 00000000000..77fbed3a42f
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/request_headers_extractor_test.go
@@ -0,0 +1,118 @@
+package devicedetection
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/51Degrees/device-detection-go/v4/dd"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestExtractEvidenceStrings(t *testing.T) {
+	tests := []struct {
+		name             string
+		headers          map[string]string
+		keys             []dd.EvidenceKey
+		expectedEvidence []stringEvidence
+	}{
+		{
+			name: "Ignored_query_evidence",
+			headers: map[string]string{
+				"User-Agent": "Mozilla/5.0",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpEvidenceQuery, Key: "User-Agent"},
+			},
+			expectedEvidence: []stringEvidence{},
+		},
+		{
+			name:    "Empty_headers",
+			headers: map[string]string{},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: "User-Agent"},
+			},
+			expectedEvidence: []stringEvidence{},
+		},
+		{
+			name: "Single_header",
+			headers: map[string]string{
+				"User-Agent": "Mozilla/5.0",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: "User-Agent"},
+			},
+			expectedEvidence: []stringEvidence{
+				{Prefix: headerPrefix, Key: "User-Agent", Value: "Mozilla/5.0"},
+			},
+		},
+		{
+			name: "Multiple_headers",
+			headers: map[string]string{
+				"User-Agent": "Mozilla/5.0",
+				"Accept":     "text/html",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: "User-Agent"},
+				{Prefix: dd.HttpEvidenceQuery, Key: "Query"},
+				{Prefix: dd.HttpHeaderString, Key: "Accept"},
+			},
+			expectedEvidence: []stringEvidence{
+				{Prefix: headerPrefix, Key: "User-Agent", Value: "Mozilla/5.0"},
+				{Prefix: headerPrefix, Key: "Accept", Value: "text/html"},
+			},
+		},
+		{
+			name: "Header_with_quotes_removed",
+			headers: map[string]string{
+				"IP-List": "\"92.0.4515.159\"",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: "IP-List"},
+			},
+			expectedEvidence: []stringEvidence{
+				{Prefix: headerPrefix, Key: "IP-List", Value: "92.0.4515.159"},
+			},
+		},
+		{
+			name: "Sec-CH-UA_headers_with_quotes_left",
+			headers: map[string]string{
+				"Sec-CH-UA": "\"Chromium\";v=\"92\", \"Google Chrome\";v=\"92\"",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: secChUa},
+			},
+			expectedEvidence: []stringEvidence{
+				{Prefix: headerPrefix, Key: secChUa, Value: "\"Chromium\";v=\"92\", \"Google Chrome\";v=\"92\""},
+			},
+		},
+		{
+			name: "Sec-CH-UA-Full-Version-List_headers_with_quotes_left",
+			headers: map[string]string{
+				"Sec-CH-UA-Full-Version-List": "\"92.0.4515.159\"",
+			},
+			keys: []dd.EvidenceKey{
+				{Prefix: dd.HttpHeaderString, Key: secUaFullVersionList},
+			},
+			expectedEvidence: []stringEvidence{
+				{Prefix: headerPrefix, Key: secUaFullVersionList, Value: "\"92.0.4515.159\""},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			req := http.Request{
+				Header: make(map[string][]string),
+			}
+
+			for key, value := range test.headers {
+				req.Header.Set(key, value)
+			}
+
+			extractor := newEvidenceFromRequestHeadersExtractor()
+			evidence := extractor.extractEvidenceStrings(&req, test.keys)
+
+			assert.Equal(t, test.expectedEvidence, evidence)
+		})
+	}
+}
diff --git a/modules/fiftyonedegrees/devicedetection/sample/pbs.json b/modules/fiftyonedegrees/devicedetection/sample/pbs.json
new file mode 100644
index 00000000000..43fd28610f1
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/sample/pbs.json
@@ -0,0 +1,84 @@
+{
+    "adapters": [
+        {
+            "appnexus": {
+                "enabled": true
+            }
+        }
+    ],
+    "gdpr": {
+        "enabled": true,
+        "default_value": 0,
+        "timeouts_ms": {
+            "active_vendorlist_fetch": 900000
+        }
+    },
+    "stored_requests": {
+        "filesystem": {
+            "enabled": true,
+            "directorypath": "sample/stored"
+        }
+    },
+    "stored_responses": {
+        "filesystem": {
+            "enabled": true,
+            "directorypath": "sample/stored"
+        }
+    },
+    "hooks": {
+        "enabled": true,
+        "modules": {
+            "fiftyonedegrees": {
+                "devicedetection": {
+                    "enabled": true,
+                    "data_file": {
+                        "path": "TAC-HashV41.hash",
+                        "update": {
+                            "auto": false,
+                            "polling_interval": 3600,
+                            "license_key": "YOUR_LICENSE_KEY",
+                            "product": "V4Enterprise"
+                        }
+                    },
+                    "performance": {
+                        "profile": "InMemory"
+                    }
+                }
+            }
+        },
+        "host_execution_plan": {
+            "endpoints": {
+                "/openrtb2/auction": {
+                    "stages": {
+                        "entrypoint": {
+                            "groups": [
+                                {
+                                    "timeout": 10,
+                                    "hook_sequence": [
+                                        {
+                                            "module_code": "fiftyonedegrees.devicedetection",
+                                            "hook_impl_code": "fiftyone-devicedetection-entrypoint-hook"
+                                        }
+                                    ]
+                                }
+                            ]
+                        },
+                        "raw_auction_request": {
+                            "groups": [
+                                {
+                                    "timeout": 10,
+                                    "hook_sequence": [
+                                        {
+                                            "module_code": "fiftyonedegrees.devicedetection",
+                                            "hook_impl_code": "fiftyone-devicedetection-raw-auction-request-hook"
+                                        }
+                                    ]
+                                }
+                            ]
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/fiftyonedegrees/devicedetection/sample/request_data.json b/modules/fiftyonedegrees/devicedetection/sample/request_data.json
new file mode 100644
index 00000000000..1f6bc8900f8
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/sample/request_data.json
@@ -0,0 +1,114 @@
+{
+  "imp": [{
+      "ext": {
+        "data": {
+          "adserver": {
+            "name": "gam",
+            "adslot": "test"
+          },
+          "pbadslot": "test",
+          "gpid": "test"
+        },
+        "gpid": "test",
+        "prebid": {
+          "bidder": {
+            "appnexus": {
+              "placement_id": 1,
+              "use_pmt_rule": false
+            }
+          },
+          "adunitcode": "25e8ad9f-13a4-4404-ba74-f9eebff0e86c",
+          "floors": {
+            "floorMin": 0.01
+          }
+        }
+      },
+      "id": "2529eeea-813e-4da6-838f-f91c28d64867",
+      "banner": {
+        "topframe": 1,
+        "format": [
+          {
+            "w": 728,
+            "h": 90
+          }
+        ],
+        "pos": 1
+      },
+      "bidfloor": 0.01,
+      "bidfloorcur": "USD"
+  }],
+  "site": {
+    "domain": "test.com",
+    "publisher": {
+      "domain": "test.com",
+      "id": "1"
+    },
+    "page": "https://www.test.com/"
+  },
+  "device": {
+    "ua": "Mozilla/5.0 (Linux; Android 11; SM-G998W) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36"
+  },
+  "id": "fc4670ce-4985-4316-a245-b43c885dc37a",
+  "test": 1,
+  "cur": [
+    "USD"
+  ],
+  "source": {
+    "ext": {
+      "schain": {
+        "ver": "1.0",
+        "complete": 1,
+        "nodes": [
+          {
+            "asi": "example.com",
+            "sid": "1234",
+            "hp": 1
+          }
+        ]
+      }
+    }
+  },
+  "ext": {
+    "prebid": {
+      "cache": {
+        "bids": {
+          "returnCreative": true
+        },
+        "vastxml": {
+          "returnCreative": true
+        }
+      },
+      "auctiontimestamp": 1698390609882,
+      "targeting": {
+        "includewinners": true,
+        "includebidderkeys": false
+      },
+      "schains": [
+        {
+          "bidders": [
+            "appnexus"
+          ],
+          "schain": {
+            "ver": "1.0",
+            "complete": 1,
+            "nodes": [
+              {
+                "asi": "example.com",
+                "sid": "1234",
+                "hp": 1
+              }
+            ]
+          }
+        }
+      ],
+      "floors": {
+        "enabled": false,
+        "floorMin": 0.01,
+        "floorMinCur": "USD"
+      },
+      "createtids": false
+    }
+  },
+  "user": {},
+  "tmax": 1700
+}
\ No newline at end of file
diff --git a/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go b/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go
new file mode 100644
index 00000000000..ab69210449f
--- /dev/null
+++ b/modules/fiftyonedegrees/devicedetection/sua_payload_extractor.go
@@ -0,0 +1,144 @@
+package devicedetection
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/spf13/cast"
+	"github.com/tidwall/gjson"
+)
+
+const (
+	secChUaArch            = "Sec-Ch-Ua-Arch"
+	secChUaMobile          = "Sec-Ch-Ua-Mobile"
+	secChUaModel           = "Sec-Ch-Ua-Model"
+	secChUaPlatform        = "Sec-Ch-Ua-Platform"
+	secUaFullVersionList   = "Sec-Ch-Ua-Full-Version-List"
+	secChUaPlatformVersion = "Sec-Ch-Ua-Platform-Version"
+	secChUa                = "Sec-Ch-Ua"
+
+	userAgentHeader = "User-Agent"
+)
+
+// evidenceFromSUAPayloadExtractor extracts evidence from the SUA payload of device
+type evidenceFromSUAPayloadExtractor struct{}
+
+func newEvidenceFromSUAPayloadExtractor() evidenceFromSUAPayloadExtractor {
+	return evidenceFromSUAPayloadExtractor{}
+}
+
+// Extract extracts evidence from the SUA payload
+func (x evidenceFromSUAPayloadExtractor) extract(payload []byte) []stringEvidence {
+	if payload != nil {
+		return x.extractEvidenceStrings(payload)
+	}
+
+	return nil
+}
+
+var (
+	uaPath              = "device.ua"
+	archPath            = "device.sua.architecture"
+	mobilePath          = "device.sua.mobile"
+	modelPath           = "device.sua.model"
+	platformBrandPath   = "device.sua.platform.brand"
+	platformVersionPath = "device.sua.platform.version"
+	browsersPath        = "device.sua.browsers"
+)
+
+// extractEvidenceStrings extracts evidence from the SUA payload
+func (x evidenceFromSUAPayloadExtractor) extractEvidenceStrings(payload []byte) []stringEvidence {
+	res := make([]stringEvidence, 0, 10)
+
+	uaResult := gjson.GetBytes(payload, uaPath)
+	if uaResult.Exists() {
+		res = append(
+			res,
+			stringEvidence{Prefix: headerPrefix, Key: userAgentHeader, Value: uaResult.String()},
+		)
+	}
+
+	archResult := gjson.GetBytes(payload, archPath)
+	if archResult.Exists() {
+		res = x.appendEvidenceIfExists(res, secChUaArch, archResult.String())
+	}
+
+	mobileResult := gjson.GetBytes(payload, mobilePath)
+	if mobileResult.Exists() {
+		res = x.appendEvidenceIfExists(res, secChUaMobile, mobileResult.String())
+	}
+
+	modelResult := gjson.GetBytes(payload, modelPath)
+	if modelResult.Exists() {
+		res = x.appendEvidenceIfExists(res, secChUaModel, modelResult.String())
+	}
+
+	platformBrandResult := gjson.GetBytes(payload, platformBrandPath)
+	if platformBrandResult.Exists() {
+		res = x.appendEvidenceIfExists(res, secChUaPlatform, platformBrandResult.String())
+	}
+
+	platformVersionResult := gjson.GetBytes(payload, platformVersionPath)
+	if platformVersionResult.Exists() {
+		res = x.appendEvidenceIfExists(
+			res,
+			secChUaPlatformVersion,
+			strings.Join(resultToStringArray(platformVersionResult.Array()), "."),
+		)
+	}
+
+	browsersResult := gjson.GetBytes(payload, browsersPath)
+	if browsersResult.Exists() {
+		res = x.appendEvidenceIfExists(res, secUaFullVersionList, x.extractBrowsers(browsersResult))
+
+	}
+
+	return res
+}
+
+func resultToStringArray(array []gjson.Result) []string {
+	strArray := make([]string, len(array))
+	for i, result := range array {
+		strArray[i] = result.String()
+	}
+
+	return strArray
+}
+
+// appendEvidenceIfExists appends evidence to the destination if the value is not nil
+func (x evidenceFromSUAPayloadExtractor) appendEvidenceIfExists(destination []stringEvidence, name string, value interface{}) []stringEvidence {
+	if value != nil {
+		valStr := cast.ToString(value)
+		if len(valStr) == 0 {
+			return destination
+		}
+
+		return append(
+			destination,
+			stringEvidence{Prefix: headerPrefix, Key: name, Value: valStr},
+		)
+	}
+
+	return destination
+}
+
+// extractBrowsers extracts browsers from the SUA payload
+func (x evidenceFromSUAPayloadExtractor) extractBrowsers(browsers gjson.Result) string {
+	if !browsers.IsArray() {
+		return ""
+	}
+
+	browsersRaw := make([]string, len(browsers.Array()))
+
+	for i, result := range browsers.Array() {
+		brand := result.Get("brand").String()
+		versionsRaw := result.Get("version").Array()
+		versions := resultToStringArray(versionsRaw)
+
+		browsersRaw[i] = fmt.Sprintf(`"%s";v="%s"`, brand, strings.Join(versions, "."))
+	}
+
+	res := strings.Join(browsersRaw, ",")
+
+	return res
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 173120f7301..f7706ce5c25 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -29,6 +29,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderAdkernel,
 	BidderAdkernelAdn,
 	BidderAdman,
+	BidderAdmatic,
 	BidderAdmixer,
 	BidderAdnuntius,
 	BidderAdOcean,
@@ -42,6 +43,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderAdtarget,
 	BidderAdtrgtme,
 	BidderAdtelligent,
+	BidderAdTonos,
 	BidderAdvangelists,
 	BidderAdView,
 	BidderAdxcg,
@@ -54,6 +56,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderApacdex,
 	BidderAppnexus,
 	BidderAppush,
+	BidderAso,
 	BidderAudienceNetwork,
 	BidderAutomatad,
 	BidderAvocet,
@@ -65,10 +68,12 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderBetween,
 	BidderBeyondMedia,
 	BidderBidmachine,
+	BidderBidmatic,
 	BidderBidmyadz,
 	BidderBidsCube,
 	BidderBidstack,
-	BidderBizzclick,
+	BidderBigoAd,
+	BidderBlasto,
 	BidderBliink,
 	BidderBlue,
 	BidderBluesea,
@@ -78,12 +83,15 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderBWX,
 	BidderCadentApertureMX,
 	BidderCcx,
+	BidderCointraffic,
 	BidderCoinzilla,
 	BidderColossus,
 	BidderCompass,
+	BidderConcert,
 	BidderConnectAd,
 	BidderConsumable,
 	BidderConversant,
+	BidderCopper6ssp,
 	BidderCpmstar,
 	BidderCriteo,
 	BidderCWire,
@@ -92,13 +100,16 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderDeepintent,
 	BidderDefinemedia,
 	BidderDianomi,
+	BidderDisplayio,
 	BidderEdge226,
 	BidderDmx,
 	BidderDXKulture,
+	BidderDriftPixel,
 	BidderEmtv,
 	BidderEmxDigital,
 	BidderEPlanning,
 	BidderEpom,
+	BidderEscalax,
 	BidderEVolution,
 	BidderFlipp,
 	BidderFreewheelSSP,
@@ -128,20 +139,24 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderLmKiviads,
 	BidderKrushmedia,
 	BidderLemmadigital,
-	BidderLiftoff,
 	BidderLimelightDigital,
 	BidderLockerDome,
 	BidderLogan,
 	BidderLogicad,
+	BidderLoyal,
 	BidderLunaMedia,
 	BidderMabidder,
 	BidderMadvertise,
 	BidderMarsmedia,
 	BidderMediafuse,
+	BidderMediaGo,
 	BidderMedianet,
+	BidderMeloZen,
+	BidderMetaX,
 	BidderMgid,
 	BidderMgidX,
 	BidderMinuteMedia,
+	BidderMissena,
 	BidderMobfoxpb,
 	BidderMobileFuse,
 	BidderMotorik,
@@ -152,19 +167,25 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderOpenWeb,
 	BidderOpenx,
 	BidderOperaads,
+	BidderOraki,
 	BidderOrbidder,
 	BidderOutbrain,
 	BidderOwnAdx,
 	BidderPangle,
 	BidderPGAMSsp,
+	BidderPlaydigo,
 	BidderPubmatic,
+	BidderPubrise,
 	BidderPubnative,
 	BidderPulsepoint,
 	BidderPWBid,
+	BidderQT,
+	BidderReadpeak,
 	BidderRelevantDigital,
 	BidderRevcontent,
 	BidderRichaudience,
 	BidderRise,
+	BidderRoulax,
 	BidderRTBHouse,
 	BidderRubicon,
 	BidderSeedingAlliance,
@@ -180,6 +201,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderSmartx,
 	BidderSmartyAds,
 	BidderSmileWanted,
+	BidderSmrtconnect,
 	BidderSonobi,
 	BidderSovrn,
 	BidderSovrnXsp,
@@ -189,14 +211,18 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderTappx,
 	BidderTeads,
 	BidderTelaria,
+	BidderTheadx,
+	BidderTheTradeDesk,
 	BidderTpmn,
 	BidderTrafficGate,
 	BidderTriplelift,
 	BidderTripleliftNative,
+	BidderTrustedstack,
 	BidderUcfunnel,
 	BidderUndertone,
 	BidderUnicorn,
 	BidderUnruly,
+	BidderVidazoo,
 	BidderVideoByte,
 	BidderVideoHeroes,
 	BidderVidoomy,
@@ -204,6 +230,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderVisx,
 	BidderVox,
 	BidderVrtcal,
+	BidderVungle,
 	BidderXeworks,
 	BidderYahooAds,
 	BidderYandex,
@@ -292,6 +319,28 @@ func IsBidderNameReserved(name string) bool {
 	return false
 }
 
+// IsPotentialBidder returns true if the name is not reserved witbin the imp[].ext context
+func IsPotentialBidder(name string) bool {
+	switch BidderName(name) {
+	case BidderReservedContext:
+		return false
+	case BidderReservedData:
+		return false
+	case BidderReservedGPID:
+		return false
+	case BidderReservedPrebid:
+		return false
+	case BidderReservedSKAdN:
+		return false
+	case BidderReservedTID:
+		return false
+	case BidderReservedAE:
+		return false
+	default:
+		return true
+	}
+}
+
 // Names of core bidders. These names *must* match the bidder code in Prebid.js if an adapter also exists in that
 // project. You may *not* use the name 'general' as that is reserved for general error messages nor 'context' as
 // that is reserved for first party data.
@@ -309,6 +358,7 @@ const (
 	BidderAdkernel          BidderName = "adkernel"
 	BidderAdkernelAdn       BidderName = "adkernelAdn"
 	BidderAdman             BidderName = "adman"
+	BidderAdmatic           BidderName = "admatic"
 	BidderAdmixer           BidderName = "admixer"
 	BidderAdnuntius         BidderName = "adnuntius"
 	BidderAdOcean           BidderName = "adocean"
@@ -321,6 +371,7 @@ const (
 	BidderAdsinteractive    BidderName = "adsinteractive"
 	BidderAdtarget          BidderName = "adtarget"
 	BidderAdtrgtme          BidderName = "adtrgtme"
+	BidderAdTonos           BidderName = "adtonos"
 	BidderAdtelligent       BidderName = "adtelligent"
 	BidderAdvangelists      BidderName = "advangelists"
 	BidderAdView            BidderName = "adview"
@@ -334,6 +385,7 @@ const (
 	BidderApacdex           BidderName = "apacdex"
 	BidderAppnexus          BidderName = "appnexus"
 	BidderAppush            BidderName = "appush"
+	BidderAso               BidderName = "aso"
 	BidderAudienceNetwork   BidderName = "audienceNetwork"
 	BidderAutomatad         BidderName = "automatad"
 	BidderAvocet            BidderName = "avocet"
@@ -345,10 +397,12 @@ const (
 	BidderBetween           BidderName = "between"
 	BidderBeyondMedia       BidderName = "beyondmedia"
 	BidderBidmachine        BidderName = "bidmachine"
+	BidderBidmatic          BidderName = "bidmatic"
 	BidderBidmyadz          BidderName = "bidmyadz"
 	BidderBidsCube          BidderName = "bidscube"
 	BidderBidstack          BidderName = "bidstack"
-	BidderBizzclick         BidderName = "bizzclick"
+	BidderBigoAd            BidderName = "bigoad"
+	BidderBlasto            BidderName = "blasto"
 	BidderBliink            BidderName = "bliink"
 	BidderBlue              BidderName = "blue"
 	BidderBluesea           BidderName = "bluesea"
@@ -358,12 +412,15 @@ const (
 	BidderBWX               BidderName = "bwx"
 	BidderCadentApertureMX  BidderName = "cadent_aperture_mx"
 	BidderCcx               BidderName = "ccx"
+	BidderCointraffic       BidderName = "cointraffic"
 	BidderCoinzilla         BidderName = "coinzilla"
 	BidderColossus          BidderName = "colossus"
 	BidderCompass           BidderName = "compass"
+	BidderConcert           BidderName = "concert"
 	BidderConnectAd         BidderName = "connectad"
 	BidderConsumable        BidderName = "consumable"
 	BidderConversant        BidderName = "conversant"
+	BidderCopper6ssp        BidderName = "copper6ssp"
 	BidderCpmstar           BidderName = "cpmstar"
 	BidderCriteo            BidderName = "criteo"
 	BidderCWire             BidderName = "cwire"
@@ -372,13 +429,16 @@ const (
 	BidderDeepintent        BidderName = "deepintent"
 	BidderDefinemedia       BidderName = "definemedia"
 	BidderDianomi           BidderName = "dianomi"
+	BidderDisplayio         BidderName = "displayio"
 	BidderEdge226           BidderName = "edge226"
 	BidderDmx               BidderName = "dmx"
 	BidderDXKulture         BidderName = "dxkulture"
+	BidderDriftPixel        BidderName = "driftpixel"
 	BidderEmtv              BidderName = "emtv"
 	BidderEmxDigital        BidderName = "emx_digital"
 	BidderEPlanning         BidderName = "eplanning"
 	BidderEpom              BidderName = "epom"
+	BidderEscalax           BidderName = "escalax"
 	BidderEVolution         BidderName = "e_volution"
 	BidderFlipp             BidderName = "flipp"
 	BidderFreewheelSSP      BidderName = "freewheelssp"
@@ -408,20 +468,24 @@ const (
 	BidderLmKiviads         BidderName = "lm_kiviads"
 	BidderKrushmedia        BidderName = "krushmedia"
 	BidderLemmadigital      BidderName = "lemmadigital"
-	BidderLiftoff           BidderName = "liftoff"
 	BidderLimelightDigital  BidderName = "limelightDigital"
 	BidderLockerDome        BidderName = "lockerdome"
 	BidderLogan             BidderName = "logan"
 	BidderLogicad           BidderName = "logicad"
+	BidderLoyal             BidderName = "loyal"
 	BidderLunaMedia         BidderName = "lunamedia"
 	BidderMabidder          BidderName = "mabidder"
 	BidderMadvertise        BidderName = "madvertise"
 	BidderMarsmedia         BidderName = "marsmedia"
 	BidderMediafuse         BidderName = "mediafuse"
+	BidderMediaGo           BidderName = "mediago"
 	BidderMedianet          BidderName = "medianet"
+	BidderMeloZen           BidderName = "melozen"
+	BidderMetaX             BidderName = "metax"
 	BidderMgid              BidderName = "mgid"
 	BidderMgidX             BidderName = "mgidX"
 	BidderMinuteMedia       BidderName = "minutemedia"
+	BidderMissena           BidderName = "missena"
 	BidderMobfoxpb          BidderName = "mobfoxpb"
 	BidderMobileFuse        BidderName = "mobilefuse"
 	BidderMotorik           BidderName = "motorik"
@@ -432,19 +496,25 @@ const (
 	BidderOpenWeb           BidderName = "openweb"
 	BidderOpenx             BidderName = "openx"
 	BidderOperaads          BidderName = "operaads"
+	BidderOraki             BidderName = "oraki"
 	BidderOrbidder          BidderName = "orbidder"
 	BidderOutbrain          BidderName = "outbrain"
 	BidderOwnAdx            BidderName = "ownadx"
 	BidderPangle            BidderName = "pangle"
 	BidderPGAMSsp           BidderName = "pgamssp"
+	BidderPlaydigo          BidderName = "playdigo"
 	BidderPubmatic          BidderName = "pubmatic"
+	BidderPubrise           BidderName = "pubrise"
 	BidderPubnative         BidderName = "pubnative"
 	BidderPulsepoint        BidderName = "pulsepoint"
 	BidderPWBid             BidderName = "pwbid"
+	BidderQT                BidderName = "qt"
+	BidderReadpeak          BidderName = "readpeak"
 	BidderRelevantDigital   BidderName = "relevantdigital"
 	BidderRevcontent        BidderName = "revcontent"
 	BidderRichaudience      BidderName = "richaudience"
 	BidderRise              BidderName = "rise"
+	BidderRoulax            BidderName = "roulax"
 	BidderRTBHouse          BidderName = "rtbhouse"
 	BidderRubicon           BidderName = "rubicon"
 	BidderSeedingAlliance   BidderName = "seedingAlliance"
@@ -460,6 +530,7 @@ const (
 	BidderSmartx            BidderName = "smartx"
 	BidderSmartyAds         BidderName = "smartyads"
 	BidderSmileWanted       BidderName = "smilewanted"
+	BidderSmrtconnect       BidderName = "smrtconnect"
 	BidderSonobi            BidderName = "sonobi"
 	BidderSovrn             BidderName = "sovrn"
 	BidderSovrnXsp          BidderName = "sovrnXsp"
@@ -469,14 +540,18 @@ const (
 	BidderTappx             BidderName = "tappx"
 	BidderTeads             BidderName = "teads"
 	BidderTelaria           BidderName = "telaria"
+	BidderTheadx            BidderName = "theadx"
+	BidderTheTradeDesk      BidderName = "thetradedesk"
 	BidderTpmn              BidderName = "tpmn"
 	BidderTrafficGate       BidderName = "trafficgate"
 	BidderTriplelift        BidderName = "triplelift"
 	BidderTripleliftNative  BidderName = "triplelift_native"
+	BidderTrustedstack      BidderName = "trustedstack"
 	BidderUcfunnel          BidderName = "ucfunnel"
 	BidderUndertone         BidderName = "undertone"
 	BidderUnicorn           BidderName = "unicorn"
 	BidderUnruly            BidderName = "unruly"
+	BidderVidazoo           BidderName = "vidazoo"
 	BidderVideoByte         BidderName = "videobyte"
 	BidderVideoHeroes       BidderName = "videoheroes"
 	BidderVidoomy           BidderName = "vidoomy"
@@ -484,6 +559,7 @@ const (
 	BidderVisx              BidderName = "visx"
 	BidderVox               BidderName = "vox"
 	BidderVrtcal            BidderName = "vrtcal"
+	BidderVungle            BidderName = "vungle"
 	BidderXeworks           BidderName = "xeworks"
 	BidderYahooAds          BidderName = "yahooAds"
 	BidderYandex            BidderName = "yandex"
@@ -539,6 +615,8 @@ var bidderNameLookup = func() map[string]BidderName {
 	return lookup
 }()
 
+type BidderNameNormalizer func(name string) (BidderName, bool)
+
 func NormalizeBidderName(name string) (BidderName, bool) {
 	nameLower := strings.ToLower(name)
 	bidderName, exists := bidderNameLookup[nameLower]
@@ -612,6 +690,7 @@ func NewBidderParamsValidator(schemaDirectory string) (BidderParamValidator, err
 		if _, ok := bidderMap[bidderName]; !ok {
 			return nil, fmt.Errorf("File %s/%s does not match a valid BidderName.", schemaDirectory, fileInfo.Name())
 		}
+
 		toOpen, err := paramsValidator.abs(filepath.Join(schemaDirectory, fileInfo.Name()))
 		if err != nil {
 			return nil, fmt.Errorf("Failed to get an absolute representation of the path: %s, %v", toOpen, err)
diff --git a/openrtb_ext/convert_down.go b/openrtb_ext/convert_down.go
index e0842978551..bfb6028d8c7 100644
--- a/openrtb_ext/convert_down.go
+++ b/openrtb_ext/convert_down.go
@@ -31,13 +31,6 @@ func ConvertDownTo25(r *RequestWrapper) error {
 		}
 	}
 
-	// Remove fields introduced in OpenRTB 2.6+. The previous OpenRTB 2.5 spec did not specify that
-	// bidders must tolerate new or unexpected fields.
-	clear26Fields(r)
-	clear202211Fields(r)
-	clear202303Fields(r)
-	clear202309Fields(r)
-
 	return nil
 }
 
diff --git a/openrtb_ext/convert_down_test.go b/openrtb_ext/convert_down_test.go
index 1bf112dcb3a..346ad4816ed 100644
--- a/openrtb_ext/convert_down_test.go
+++ b/openrtb_ext/convert_down_test.go
@@ -35,50 +35,6 @@ func TestConvertDownTo25(t *testing.T) {
 				User:   &openrtb2.User{Ext: json.RawMessage(`{"consent":"1","eids":[{"source":"42"}]}`)},
 			},
 		},
-		{
-			name: "2.6-dropped", // integration with clear26Fields
-			givenRequest: openrtb2.BidRequest{
-				ID:     "anyID",
-				CatTax: adcom1.CatTaxIABContent10,
-				Device: &openrtb2.Device{LangB: "anyLang"},
-			},
-			expectedRequest: openrtb2.BidRequest{
-				ID:     "anyID",
-				Device: &openrtb2.Device{},
-			},
-		},
-		{
-			name: "2.6-202211-dropped", // integration with clear202211Fields
-			givenRequest: openrtb2.BidRequest{
-				ID:  "anyID",
-				App: &openrtb2.App{InventoryPartnerDomain: "anyDomain"},
-			},
-			expectedRequest: openrtb2.BidRequest{
-				ID:  "anyID",
-				App: &openrtb2.App{},
-			},
-		},
-		{
-			name: "2.6-202303-dropped", // integration with clear202303Fields
-			givenRequest: openrtb2.BidRequest{
-				ID:  "anyID",
-				Imp: []openrtb2.Imp{{ID: "1", Refresh: &openrtb2.Refresh{Count: ptrutil.ToPtr(1)}}},
-			},
-			expectedRequest: openrtb2.BidRequest{
-				ID:  "anyID",
-				Imp: []openrtb2.Imp{{ID: "1"}},
-			},
-		},
-		{
-			name: "2.6-202309-dropped", // integration with clear202309Fields
-			givenRequest: openrtb2.BidRequest{
-				ID:   "anyID",
-				ACat: []string{"anyACat"},
-			},
-			expectedRequest: openrtb2.BidRequest{
-				ID: "anyID",
-			},
-		},
 		{
 			name: "2.6-to-2.5-OtherExtFields",
 			givenRequest: openrtb2.BidRequest{
diff --git a/openrtb_ext/imp_adtonos.go b/openrtb_ext/imp_adtonos.go
new file mode 100644
index 00000000000..f59ee35b329
--- /dev/null
+++ b/openrtb_ext/imp_adtonos.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtAdTonos struct {
+	SupplierID string `json:"supplierId"`
+}
diff --git a/openrtb_ext/imp_bidmatic.go b/openrtb_ext/imp_bidmatic.go
new file mode 100644
index 00000000000..935c977e7ac
--- /dev/null
+++ b/openrtb_ext/imp_bidmatic.go
@@ -0,0 +1,11 @@
+package openrtb_ext
+
+import "encoding/json"
+
+// ExtImpBidmatic defines the contract for bidrequest.imp[i].ext.prebid.bidder.bidmatic
+type ExtImpBidmatic struct {
+	SourceId    json.Number `json:"source"`
+	PlacementId int         `json:"placementId,omitempty"`
+	SiteId      int         `json:"siteId,omitempty"`
+	BidFloor    float64     `json:"bidFloor,omitempty"`
+}
diff --git a/openrtb_ext/imp_connectad.go b/openrtb_ext/imp_connectad.go
index c4c7ab696f2..d530534cf4f 100644
--- a/openrtb_ext/imp_connectad.go
+++ b/openrtb_ext/imp_connectad.go
@@ -1,7 +1,9 @@
 package openrtb_ext
 
+import "github.com/prebid/prebid-server/v2/util/jsonutil"
+
 type ExtImpConnectAd struct {
-	NetworkID int     `json:"networkId"`
-	SiteID    int     `json:"siteId"`
-	Bidfloor  float64 `json:"bidfloor,omitempty"`
+	NetworkID jsonutil.StringInt `json:"networkId"`
+	SiteID    jsonutil.StringInt `json:"siteId"`
+	Bidfloor  float64            `json:"bidfloor,omitempty"`
 }
diff --git a/openrtb_ext/imp_copper6ssp.go b/openrtb_ext/imp_copper6ssp.go
new file mode 100644
index 00000000000..a9fd47a1eb6
--- /dev/null
+++ b/openrtb_ext/imp_copper6ssp.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ImpExtCopper6ssp struct {
+	PlacementID string `json:"placementId"`
+	EndpointID  string `json:"endpointId"`
+}
diff --git a/openrtb_ext/imp_escalax.go b/openrtb_ext/imp_escalax.go
new file mode 100644
index 00000000000..15292b59552
--- /dev/null
+++ b/openrtb_ext/imp_escalax.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ExtEscalax struct {
+	AccountID string `json:"accountId"`
+	SourceID  string `json:"sourceId"`
+}
diff --git a/openrtb_ext/imp_melozen.go b/openrtb_ext/imp_melozen.go
new file mode 100644
index 00000000000..598df6a28e9
--- /dev/null
+++ b/openrtb_ext/imp_melozen.go
@@ -0,0 +1,5 @@
+package openrtb_ext
+
+type ImpExtMeloZen struct {
+	PubId string `json:"pubId"`
+}
diff --git a/openrtb_ext/imp_missena.go b/openrtb_ext/imp_missena.go
new file mode 100644
index 00000000000..3e341957123
--- /dev/null
+++ b/openrtb_ext/imp_missena.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+type ExtImpMissena struct {
+	ApiKey    string `json:"apiKey"`
+	Placement string `json:"placement"`
+	TestMode  string `json:"test"`
+}
diff --git a/openrtb_ext/imp_oraki.go b/openrtb_ext/imp_oraki.go
new file mode 100644
index 00000000000..a9dea04434f
--- /dev/null
+++ b/openrtb_ext/imp_oraki.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ImpExtOraki struct {
+	PlacementID string `json:"placementId"`
+	EndpointID  string `json:"endpointId"`
+}
diff --git a/openrtb_ext/imp_pubrise.go b/openrtb_ext/imp_pubrise.go
new file mode 100644
index 00000000000..c2b30391748
--- /dev/null
+++ b/openrtb_ext/imp_pubrise.go
@@ -0,0 +1,6 @@
+package openrtb_ext
+
+type ImpExtPubrise struct {
+	PlacementID string `json:"placementId"`
+	EndpointID  string `json:"endpointId"`
+}
diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go
index d7ff5acc021..58527528ec6 100644
--- a/openrtb_ext/request_wrapper.go
+++ b/openrtb_ext/request_wrapper.go
@@ -3,12 +3,12 @@ package openrtb_ext
 import (
 	"encoding/json"
 	"errors"
+	"maps"
+	"slices"
 
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v2/util/jsonutil"
-	"github.com/prebid/prebid-server/v2/util/maputil"
 	"github.com/prebid/prebid-server/v2/util/ptrutil"
-	"github.com/prebid/prebid-server/v2/util/sliceutil"
 )
 
 // RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they
@@ -60,6 +60,8 @@ const (
 	dataKey                             = "data"
 	schainKey                           = "schain"
 	us_privacyKey                       = "us_privacy"
+	cdepKey                             = "cdep"
+	gpcKey                              = "gpc"
 )
 
 // LenImp returns the number of impressions without causing the creation of ImpWrapper objects.
@@ -93,6 +95,12 @@ func (rw *RequestWrapper) GetImp() []*ImpWrapper {
 
 func (rw *RequestWrapper) SetImp(imps []*ImpWrapper) {
 	rw.impWrappers = imps
+	imparr := make([]openrtb2.Imp, len(imps))
+	for i, iw := range imps {
+		imparr[i] = *iw.Imp
+		iw.Imp = &imparr[i]
+	}
+	rw.Imp = imparr
 	rw.impWrappersAccessed = true
 }
 
@@ -237,6 +245,7 @@ func (rw *RequestWrapper) rebuildImp() error {
 			return err
 		}
 		rw.Imp[i] = *rw.impWrappers[i].Imp
+		rw.impWrappers[i].Imp = &rw.Imp[i]
 	}
 
 	return nil
@@ -390,6 +399,8 @@ func (rw *RequestWrapper) rebuildSourceExt() error {
 	return nil
 }
 
+// Clone clones the request wrapper exts and the imp wrappers
+// the cloned imp wrappers are pointing to the bid request imps
 func (rw *RequestWrapper) Clone() *RequestWrapper {
 	if rw == nil {
 		return nil
@@ -412,6 +423,26 @@ func (rw *RequestWrapper) Clone() *RequestWrapper {
 	return &clone
 }
 
+func (rw *RequestWrapper) CloneAndClearImpWrappers() *RequestWrapper {
+	if rw == nil {
+		return nil
+	}
+	rw.impWrappersAccessed = false
+
+	clone := *rw
+	clone.impWrappers = nil
+	clone.userExt = rw.userExt.Clone()
+	clone.deviceExt = rw.deviceExt.Clone()
+	clone.requestExt = rw.requestExt.Clone()
+	clone.appExt = rw.appExt.Clone()
+	clone.regExt = rw.regExt.Clone()
+	clone.siteExt = rw.siteExt.Clone()
+	clone.doohExt = rw.doohExt.Clone()
+	clone.sourceExt = rw.sourceExt.Clone()
+
+	return &clone
+}
+
 // ---------------------------------------------------------------
 // UserExt provides an interface for request.user.ext
 // ---------------------------------------------------------------
@@ -648,7 +679,6 @@ func (ue *UserExt) SetConsentedProvidersSettingsOut(cpSettings *ConsentedProvide
 
 	ue.consentedProvidersSettingsOut = cpSettings
 	ue.consentedProvidersSettingsOutDirty = true
-	return
 }
 
 func (ue *UserExt) GetPrebid() *ExtUserPrebid {
@@ -682,7 +712,7 @@ func (ue *UserExt) Clone() *UserExt {
 		return nil
 	}
 	clone := *ue
-	clone.ext = maputil.Clone(ue.ext)
+	clone.ext = maps.Clone(ue.ext)
 
 	if ue.consent != nil {
 		clonedConsent := *ue.consent
@@ -691,14 +721,14 @@ func (ue *UserExt) Clone() *UserExt {
 
 	if ue.prebid != nil {
 		clone.prebid = &ExtUserPrebid{}
-		clone.prebid.BuyerUIDs = maputil.Clone(ue.prebid.BuyerUIDs)
+		clone.prebid.BuyerUIDs = maps.Clone(ue.prebid.BuyerUIDs)
 	}
 
 	if ue.eids != nil {
 		clonedEids := make([]openrtb2.EID, len(*ue.eids))
 		for i, eid := range *ue.eids {
 			newEid := eid
-			newEid.UIDs = sliceutil.Clone(eid.UIDs)
+			newEid.UIDs = slices.Clone(eid.UIDs)
 			clonedEids[i] = newEid
 		}
 		clone.eids = &clonedEids
@@ -708,7 +738,7 @@ func (ue *UserExt) Clone() *UserExt {
 		clone.consentedProvidersSettingsIn = &ConsentedProvidersSettingsIn{ConsentedProvidersString: ue.consentedProvidersSettingsIn.ConsentedProvidersString}
 	}
 	if ue.consentedProvidersSettingsOut != nil {
-		clone.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{ConsentedProvidersList: sliceutil.Clone(ue.consentedProvidersSettingsOut.ConsentedProvidersList)}
+		clone.consentedProvidersSettingsOut = &ConsentedProvidersSettingsOut{ConsentedProvidersList: slices.Clone(ue.consentedProvidersSettingsOut.ConsentedProvidersList)}
 	}
 
 	return &clone
@@ -859,7 +889,7 @@ func (re *RequestExt) Clone() *RequestExt {
 	}
 
 	clone := *re
-	clone.ext = maputil.Clone(re.ext)
+	clone.ext = maps.Clone(re.ext)
 
 	if re.prebid != nil {
 		clone.prebid = re.prebid.Clone()
@@ -883,6 +913,8 @@ type DeviceExt struct {
 	extDirty    bool
 	prebid      *ExtDevicePrebid
 	prebidDirty bool
+	cdep        string
+	cdepDirty   bool
 }
 
 func (de *DeviceExt) unmarshal(extJson json.RawMessage) error {
@@ -910,6 +942,13 @@ func (de *DeviceExt) unmarshal(extJson json.RawMessage) error {
 		}
 	}
 
+	cdepJson, hasCDep := de.ext[cdepKey]
+	if hasCDep && cdepJson != nil {
+		if err := jsonutil.Unmarshal(cdepJson, &de.cdep); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -931,6 +970,19 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) {
 		de.prebidDirty = false
 	}
 
+	if de.cdepDirty {
+		if len(de.cdep) > 0 {
+			rawjson, err := jsonutil.Marshal(de.cdep)
+			if err != nil {
+				return nil, err
+			}
+			de.ext[cdepKey] = rawjson
+		} else {
+			delete(de.ext, cdepKey)
+		}
+		de.cdepDirty = false
+	}
+
 	de.extDirty = false
 	if len(de.ext) == 0 {
 		return nil, nil
@@ -939,7 +991,7 @@ func (de *DeviceExt) marshal() (json.RawMessage, error) {
 }
 
 func (de *DeviceExt) Dirty() bool {
-	return de.extDirty || de.prebidDirty
+	return de.extDirty || de.prebidDirty || de.cdepDirty
 }
 
 func (de *DeviceExt) GetExt() map[string]json.RawMessage {
@@ -968,13 +1020,22 @@ func (de *DeviceExt) SetPrebid(prebid *ExtDevicePrebid) {
 	de.prebidDirty = true
 }
 
+func (de *DeviceExt) GetCDep() string {
+	return de.cdep
+}
+
+func (de *DeviceExt) SetCDep(cdep string) {
+	de.cdep = cdep
+	de.cdepDirty = true
+}
+
 func (de *DeviceExt) Clone() *DeviceExt {
 	if de == nil {
 		return nil
 	}
 
 	clone := *de
-	clone.ext = maputil.Clone(de.ext)
+	clone.ext = maps.Clone(de.ext)
 
 	if de.prebid != nil {
 		clonedPrebid := *de.prebid
@@ -1088,7 +1149,7 @@ func (ae *AppExt) Clone() *AppExt {
 	}
 
 	clone := *ae
-	clone.ext = maputil.Clone(ae.ext)
+	clone.ext = maps.Clone(ae.ext)
 
 	clone.prebid = ptrutil.Clone(ae.prebid)
 
@@ -1154,7 +1215,7 @@ func (de *DOOHExt) Clone() *DOOHExt {
 	}
 
 	clone := *de
-	clone.ext = maputil.Clone(de.ext)
+	clone.ext = maps.Clone(de.ext)
 
 	return &clone
 }
@@ -1170,6 +1231,8 @@ type RegExt struct {
 	dsaDirty       bool
 	gdpr           *int8
 	gdprDirty      bool
+	gpc            *string
+	gpcDirty       bool
 	usPrivacy      string
 	usPrivacyDirty bool
 }
@@ -1213,6 +1276,13 @@ func (re *RegExt) unmarshal(extJson json.RawMessage) error {
 		}
 	}
 
+	gpcJson, hasGPC := re.ext[gpcKey]
+	if hasGPC && gpcJson != nil {
+		if err := jsonutil.Unmarshal(gpcJson, &re.gpc); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -1256,6 +1326,19 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
 		re.usPrivacyDirty = false
 	}
 
+	if re.gpcDirty {
+		if re.gpc != nil {
+			rawjson, err := jsonutil.Marshal(re.gpc)
+			if err != nil {
+				return nil, err
+			}
+			re.ext[gpcKey] = rawjson
+		} else {
+			delete(re.ext, gpcKey)
+		}
+		re.gpcDirty = false
+	}
+
 	re.extDirty = false
 	if len(re.ext) == 0 {
 		return nil, nil
@@ -1264,7 +1347,7 @@ func (re *RegExt) marshal() (json.RawMessage, error) {
 }
 
 func (re *RegExt) Dirty() bool {
-	return re.extDirty || re.dsaDirty || re.gdprDirty || re.usPrivacyDirty
+	return re.extDirty || re.dsaDirty || re.gdprDirty || re.usPrivacyDirty || re.gpcDirty
 }
 
 func (re *RegExt) GetExt() map[string]json.RawMessage {
@@ -1306,6 +1389,19 @@ func (re *RegExt) SetGDPR(gdpr *int8) {
 	re.gdprDirty = true
 }
 
+func (re *RegExt) GetGPC() *string {
+	if re.gpc == nil {
+		return nil
+	}
+	gpc := *re.gpc
+	return &gpc
+}
+
+func (re *RegExt) SetGPC(gpc *string) {
+	re.gpc = gpc
+	re.gpcDirty = true
+}
+
 func (re *RegExt) GetUSPrivacy() string {
 	uSPrivacy := re.usPrivacy
 	return uSPrivacy
@@ -1322,7 +1418,7 @@ func (re *RegExt) Clone() *RegExt {
 	}
 
 	clone := *re
-	clone.ext = maputil.Clone(re.ext)
+	clone.ext = maps.Clone(re.ext)
 
 	clone.gdpr = ptrutil.Clone(re.gdpr)
 
@@ -1418,7 +1514,7 @@ func (se *SiteExt) Clone() *SiteExt {
 	}
 
 	clone := *se
-	clone.ext = maputil.Clone(se.ext)
+	clone.ext = maps.Clone(se.ext)
 	clone.amp = ptrutil.Clone(se.amp)
 
 	return &clone
@@ -1521,7 +1617,7 @@ func (se *SourceExt) Clone() *SourceExt {
 	}
 
 	clone := *se
-	clone.ext = maputil.Clone(se.ext)
+	clone.ext = maps.Clone(se.ext)
 
 	clone.schain = cloneSupplyChain(se.schain)
 
@@ -1760,7 +1856,7 @@ func (e *ImpExt) Clone() *ImpExt {
 	}
 
 	clone := *e
-	clone.ext = maputil.Clone(e.ext)
+	clone.ext = maps.Clone(e.ext)
 
 	if e.prebid != nil {
 		clonedPrebid := *e.prebid
@@ -1774,7 +1870,7 @@ func (e *ImpExt) Clone() *ImpExt {
 			}
 		}
 		clonedPrebid.IsRewardedInventory = ptrutil.Clone(e.prebid.IsRewardedInventory)
-		clonedPrebid.Bidder = maputil.Clone(e.prebid.Bidder)
+		clonedPrebid.Bidder = maps.Clone(e.prebid.Bidder)
 		clonedPrebid.Options = ptrutil.Clone(e.prebid.Options)
 		clonedPrebid.Floors = ptrutil.Clone(e.prebid.Floors)
 		clone.prebid = &clonedPrebid
diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go
index f04a51a4bdc..d2f1ff48ca9 100644
--- a/openrtb_ext/request_wrapper_test.go
+++ b/openrtb_ext/request_wrapper_test.go
@@ -198,6 +198,7 @@ func TestRebuildImp(t *testing.T) {
 		request           openrtb2.BidRequest
 		requestImpWrapper []*ImpWrapper
 		expectedRequest   openrtb2.BidRequest
+		expectedAccessed  bool
 		expectedError     string
 	}{
 		{
@@ -217,11 +218,13 @@ func TestRebuildImp(t *testing.T) {
 			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
 			requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}},
 			expectedRequest:   openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "2", Ext: prebidJson}}},
+			expectedAccessed:  true,
 		},
 		{
 			description:       "One - Accessed - Error",
 			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}}},
 			requestImpWrapper: []*ImpWrapper{{Imp: nil, impExt: &ImpExt{}}},
+			expectedAccessed:  true,
 			expectedError:     "ImpWrapper RebuildImp called on a nil Imp",
 		},
 		{
@@ -229,6 +232,7 @@ func TestRebuildImp(t *testing.T) {
 			request:           openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2"}}},
 			requestImpWrapper: []*ImpWrapper{{Imp: &openrtb2.Imp{ID: "1"}, impExt: &ImpExt{}}, {Imp: &openrtb2.Imp{ID: "2"}, impExt: &ImpExt{prebid: prebid, prebidDirty: true}}},
 			expectedRequest:   openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "1"}, {ID: "2", Ext: prebidJson}}},
+			expectedAccessed:  true,
 		},
 	}
 
@@ -247,6 +251,20 @@ func TestRebuildImp(t *testing.T) {
 			assert.NoError(t, err, test.description)
 			assert.Equal(t, test.expectedRequest, *w.BidRequest, test.description)
 		}
+
+		if test.expectedAccessed && test.expectedError == "" {
+			bidRequestImps := make(map[string]*openrtb2.Imp, 0)
+			for i, v := range w.Imp {
+				bidRequestImps[v.ID] = &w.Imp[i]
+			}
+			wrapperImps := make(map[string]*openrtb2.Imp, 0)
+			for i, v := range w.impWrappers {
+				wrapperImps[v.ID] = w.impWrappers[i].Imp
+			}
+			for k := range bidRequestImps {
+				assert.Same(t, bidRequestImps[k], wrapperImps[k], test.description)
+			}
+		}
 	}
 }
 
@@ -1856,6 +1874,37 @@ func TestImpWrapperGetImpExt(t *testing.T) {
 	}
 }
 
+func TestImpWrapperSetImp(t *testing.T) {
+	origImps := []openrtb2.Imp{
+		{ID: "imp1", TagID: "tag1"},
+		{ID: "imp2", TagID: "tag2"},
+		{ID: "imp3", TagID: "tag3"},
+	}
+	expectedImps := []openrtb2.Imp{
+		{ID: "imp1", TagID: "tag4", BidFloor: 0.5},
+		{ID: "imp1.1", TagID: "tag2", BidFloor: 0.6},
+		{ID: "imp2", TagID: "notag"},
+		{ID: "imp3", TagID: "tag3"},
+	}
+	rw := RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: origImps}}
+	iw := rw.GetImp()
+	rw.Imp[0].TagID = "tag4"
+	rw.Imp[0].BidFloor = 0.5
+	iw[1] = &ImpWrapper{Imp: &expectedImps[1]}
+	*iw[2] = ImpWrapper{Imp: &expectedImps[2]}
+	iw = append(iw, &ImpWrapper{Imp: &expectedImps[3]})
+
+	rw.SetImp(iw)
+	assert.Equal(t, expectedImps, rw.BidRequest.Imp)
+	iw = rw.GetImp()
+	// Ensure that the wrapper pointers are in sync.
+	for i := range rw.BidRequest.Imp {
+		// Assert the pointers are in sync.
+		assert.Same(t, &rw.Imp[i], iw[i].Imp)
+	}
+
+}
+
 func TestImpExtTid(t *testing.T) {
 	impExt := &ImpExt{}
 
@@ -2164,6 +2213,30 @@ func TestRebuildRegExt(t *testing.T) {
 			regExt:          RegExt{usPrivacy: "", usPrivacyDirty: true},
 			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
 		},
+		{
+			name:            "req_regs_gpc_populated_-_not_dirty_-_no_change",
+			request:         openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+			regExt:          RegExt{},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+		},
+		{
+			name:            "req_regs_gpc_populated_-_dirty_and_different-_change",
+			request:         openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+			regExt:          RegExt{gpc: &strB, gpcDirty: true},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"b"}`)}},
+		},
+		{
+			name:            "req_regs_gpc_populated_-_dirty_and_same_-_no_change",
+			request:         openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+			regExt:          RegExt{gpc: &strA, gpcDirty: true},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+		},
+		{
+			name:            "req_regs_gpc_populated_-_dirty_and_nil_-_cleared",
+			request:         openrtb2.BidRequest{Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"gpc":"a"}`)}},
+			regExt:          RegExt{gpc: nil, gpcDirty: true},
+			expectedRequest: openrtb2.BidRequest{Regs: &openrtb2.Regs{}},
+		},
 	}
 
 	for _, tt := range tests {
@@ -2184,6 +2257,7 @@ func TestRegExtUnmarshal(t *testing.T) {
 		extJson         json.RawMessage
 		expectDSA       *ExtRegsDSA
 		expectGDPR      *int8
+		expectGPC       *string
 		expectUSPrivacy string
 		expectError     bool
 	}{
@@ -2243,6 +2317,21 @@ func TestRegExtUnmarshal(t *testing.T) {
 			expectGDPR:  ptrutil.ToPtr[int8](0),
 			expectError: true,
 		},
+		// GPC
+		{
+			name:        "valid_gpc_json",
+			regExt:      &RegExt{},
+			extJson:     json.RawMessage(`{"gpc":"some_value"}`),
+			expectGPC:   ptrutil.ToPtr("some_value"),
+			expectError: false,
+		},
+		{
+			name:        "malformed_gpc_json",
+			regExt:      &RegExt{},
+			extJson:     json.RawMessage(`{"gpc":nill}`),
+			expectGPC:   nil,
+			expectError: true,
+		},
 		// us_privacy
 		{
 			name:            "valid_usprivacy_json",
@@ -2338,3 +2427,18 @@ func TestRegExtGetGDPRSetGDPR(t *testing.T) {
 	assert.Equal(t, regExtGDPR, gdpr)
 	assert.NotSame(t, regExtGDPR, gdpr)
 }
+
+func TestRegExtGetGPCSetGPC(t *testing.T) {
+	regExt := &RegExt{}
+	regExtGPC := regExt.GetGPC()
+	assert.Nil(t, regExtGPC)
+	assert.False(t, regExt.Dirty())
+
+	gpc := ptrutil.ToPtr("Gpc")
+	regExt.SetGPC(gpc)
+	assert.True(t, regExt.Dirty())
+
+	regExtGPC = regExt.GetGPC()
+	assert.Equal(t, regExtGPC, gpc)
+	assert.NotSame(t, regExtGPC, gpc)
+}
diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go
index d9baea3f4da..449ff939bf5 100644
--- a/openrtb_ext/response.go
+++ b/openrtb_ext/response.go
@@ -132,14 +132,14 @@ type NonBidExt struct {
 
 // NonBid represnts the Non Bid Reason (statusCode) for given impression ID
 type NonBid struct {
-	ImpId      string    `json:"impid"`
-	StatusCode int       `json:"statuscode"`
-	Ext        NonBidExt `json:"ext"`
+	ImpId      string     `json:"impid"`
+	StatusCode int        `json:"statuscode"`
+	Ext        *NonBidExt `json:"ext,omitempty"`
 }
 
 // SeatNonBid is collection of NonBid objects with seat information
 type SeatNonBid struct {
 	NonBid []NonBid        `json:"nonbid"`
 	Seat   string          `json:"seat"`
-	Ext    json.RawMessage `json:"ext"`
+	Ext    json.RawMessage `json:"ext,omitempty"`
 }
diff --git a/ortb/clone.go b/ortb/clone.go
index 3023169bc8c..17b82bc84f4 100644
--- a/ortb/clone.go
+++ b/ortb/clone.go
@@ -421,3 +421,19 @@ func CloneBidRequestPartial(s *openrtb2.BidRequest) *openrtb2.BidRequest {
 
 	return &c
 }
+
+func CloneRegs(s *openrtb2.Regs) *openrtb2.Regs {
+	if s == nil {
+		return nil
+	}
+
+	// Shallow Copy (Value Fields)
+	c := *s
+
+	// Deep Copy (Pointers)
+	c.GDPR = ptrutil.Clone(s.GDPR)
+	c.GPPSID = slices.Clone(s.GPPSID)
+	c.Ext = slices.Clone(s.Ext)
+
+	return &c
+}
diff --git a/ortb/clone_test.go b/ortb/clone_test.go
index 73d03614db4..f1c0cbb0087 100644
--- a/ortb/clone_test.go
+++ b/ortb/clone_test.go
@@ -1172,3 +1172,44 @@ func discoverPointerFields(t reflect.Type) []string {
 	}
 	return fields
 }
+
+func TestCloneRegs(t *testing.T) {
+	t.Run("nil", func(t *testing.T) {
+		result := CloneRegs(nil)
+		assert.Nil(t, result)
+	})
+
+	t.Run("empty", func(t *testing.T) {
+		given := &openrtb2.Regs{}
+		result := CloneRegs(given)
+		assert.Empty(t, result)
+		assert.NotSame(t, given, result)
+	})
+
+	t.Run("populated", func(t *testing.T) {
+		given := &openrtb2.Regs{
+			COPPA:     1,
+			GDPR:      ptrutil.ToPtr(int8(0)),
+			USPrivacy: "1YNN",
+			GPP:       "SomeGPPStrig",
+			GPPSID:    []int8{1, 2, 3},
+			Ext:       json.RawMessage(`{"anyField":1}`),
+		}
+		result := CloneRegs(given)
+		assert.Equal(t, given, result, "equality")
+		assert.NotSame(t, given, result, "pointer")
+		assert.NotSame(t, given.GDPR, result.GDPR, "gdpr")
+		assert.NotSame(t, given.GPPSID, result.GPPSID, "gppsid[]")
+		assert.NotSame(t, given.GPPSID[0], result.GPPSID[0], "gppsid[0]")
+		assert.NotSame(t, given.Ext, result.Ext, "ext")
+	})
+
+	t.Run("assumptions", func(t *testing.T) {
+		assert.ElementsMatch(t, discoverPointerFields(reflect.TypeOf(openrtb2.Regs{})),
+			[]string{
+				"GDPR",
+				"GPPSID",
+				"Ext",
+			})
+	})
+}
diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go
index 265d47e8595..3a0934c5722 100644
--- a/privacy/ccpa/consentwriter.go
+++ b/privacy/ccpa/consentwriter.go
@@ -2,7 +2,6 @@ package ccpa
 
 import (
 	"github.com/prebid/openrtb/v20/openrtb2"
-	"github.com/prebid/prebid-server/v2/openrtb_ext"
 )
 
 // ConsentWriter implements the old PolicyWriter interface for CCPA.
@@ -16,16 +15,14 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error {
 	if req == nil {
 		return nil
 	}
-	reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
 
 	// Set consent string in USPrivacy
 	if c.Consent != "" {
-		if regsExt, err := reqWrap.GetRegExt(); err == nil {
-			regsExt.SetUSPrivacy(c.Consent)
-		} else {
-			return err
+		if req.Regs == nil {
+			req.Regs = &openrtb2.Regs{}
 		}
+		req.Regs.USPrivacy = c.Consent
 	}
 
-	return reqWrap.RebuildRequest()
+	return nil
 }
diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go
index a92400dce53..fe3dab248ef 100644
--- a/privacy/ccpa/consentwriter_test.go
+++ b/privacy/ccpa/consentwriter_test.go
@@ -75,7 +75,9 @@ func TestConsentWriterLegacy(t *testing.T) {
 			description: "Success",
 			request:     &openrtb2.BidRequest{},
 			expected: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)},
+				Regs: &openrtb2.Regs{
+					USPrivacy: "anyConsent",
+				},
 			},
 		},
 		{
@@ -83,9 +85,12 @@ func TestConsentWriterLegacy(t *testing.T) {
 			request: &openrtb2.BidRequest{
 				Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)},
 			},
-			expectedError: true,
+			expectedError: false,
 			expected: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)},
+				Regs: &openrtb2.Regs{
+					USPrivacy: "anyConsent",
+					Ext:       json.RawMessage(`malformed}`),
+				},
 			},
 		},
 	}
diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go
index 0b719bf1455..463cf3391d8 100644
--- a/privacy/ccpa/policy.go
+++ b/privacy/ccpa/policy.go
@@ -41,15 +41,8 @@ func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppConta
 			WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
 	}
 
-	if consent == "" {
-		// Read consent from request.regs.ext
-		regsExt, err := req.GetRegExt()
-		if err != nil {
-			return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err)
-		}
-		if regsExt != nil {
-			consent = regsExt.GetUSPrivacy()
-		}
+	if consent == "" && req.Regs != nil {
+		consent = req.Regs.USPrivacy
 	}
 	// Read no sale bidders from request.ext.prebid
 	reqExt, err := req.GetRequestExt()
@@ -75,21 +68,19 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) {
 
 // Write mutates an OpenRTB bid request with the CCPA regulatory information.
 func (p Policy) Write(req *openrtb_ext.RequestWrapper) error {
-	if req == nil {
+	if req == nil || req.BidRequest == nil {
 		return nil
 	}
 
-	regsExt, err := req.GetRegExt()
-	if err != nil {
-		return err
-	}
-
 	reqExt, err := req.GetRequestExt()
 	if err != nil {
 		return err
 	}
 
-	regsExt.SetUSPrivacy(p.Consent)
+	if req.Regs == nil {
+		req.Regs = &openrtb2.Regs{}
+	}
+	req.Regs.USPrivacy = p.Consent
 	setPrebidNoSale(p.NoSaleBidders, reqExt)
 	return nil
 }
diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go
index 20d9f680ba1..c77b4ddc985 100644
--- a/privacy/ccpa/policy_test.go
+++ b/privacy/ccpa/policy_test.go
@@ -23,7 +23,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
 		{
 			description: "Success",
 			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "ABC"},
 				Ext:  json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`),
 			},
 			expectedPolicy: Policy{
@@ -83,26 +83,10 @@ func TestReadFromRequestWrapper(t *testing.T) {
 				NoSaleBidders: []string{"a", "b"},
 			},
 		},
-		{
-			description: "Malformed Regs.Ext",
-			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)},
-				Ext:  json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`),
-			},
-			expectedError: true,
-		},
-		{
-			description: "Invalid Regs.Ext Type",
-			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123`)},
-				Ext:  json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`),
-			},
-			expectedError: true,
-		},
 		{
 			description: "Nil Ext",
 			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "ABC"},
 				Ext:  nil,
 			},
 			expectedPolicy: Policy{
@@ -113,7 +97,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
 		{
 			description: "Empty Ext",
 			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "ABC"},
 				Ext:  json.RawMessage(`{}`),
 			},
 			expectedPolicy: Policy{
@@ -124,7 +108,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
 		{
 			description: "Missing Ext.Prebid No Sale Value",
 			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "ABC"},
 				Ext:  json.RawMessage(`{"anythingElse":"42"}`),
 			},
 			expectedPolicy: Policy{
@@ -148,15 +132,6 @@ func TestReadFromRequestWrapper(t *testing.T) {
 			},
 			expectedError: true,
 		},
-		{
-			description: "Injection Attack",
-			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)},
-			},
-			expectedPolicy: Policy{
-				Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
-			},
-		},
 		{
 			description: "GPP Success",
 			request: &openrtb2.BidRequest{
@@ -244,7 +219,7 @@ func TestReadFromRequest(t *testing.T) {
 		{
 			description: "Success",
 			request: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "ABC"},
 				Ext:  json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`),
 			},
 			expectedPolicy: Policy{
@@ -353,7 +328,7 @@ func TestWrite(t *testing.T) {
 			policy:      Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}},
 			request:     &openrtb2.BidRequest{},
 			expected: &openrtb2.BidRequest{
-				Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)},
+				Regs: &openrtb2.Regs{USPrivacy: "anyConsent"},
 				Ext:  json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`),
 			},
 		},
diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go
index 243a6cf79e9..8269352355d 100644
--- a/privacy/gdpr/consentwriter.go
+++ b/privacy/gdpr/consentwriter.go
@@ -2,13 +2,12 @@ package gdpr
 
 import (
 	"github.com/prebid/openrtb/v20/openrtb2"
-	"github.com/prebid/prebid-server/v2/openrtb_ext"
 )
 
 // ConsentWriter implements the PolicyWriter interface for GDPR TCF.
 type ConsentWriter struct {
-	Consent    string
-	RegExtGDPR *int8
+	Consent string
+	GDPR    *int8
 }
 
 // Write mutates an OpenRTB bid request with the GDPR TCF consent.
@@ -16,26 +15,19 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error {
 	if req == nil {
 		return nil
 	}
-	reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req}
 
-	if c.RegExtGDPR != nil {
-		if regsExt, err := reqWrap.GetRegExt(); err == nil {
-			regsExt.SetGDPR(c.RegExtGDPR)
-		} else {
-			return err
+	if c.GDPR != nil {
+		if req.Regs == nil {
+			req.Regs = &openrtb2.Regs{}
 		}
+		req.Regs.GDPR = c.GDPR
 	}
 
 	if c.Consent != "" {
-		if userExt, err := reqWrap.GetUserExt(); err == nil {
-			userExt.SetConsent(&c.Consent)
-		} else {
-			return err
+		if req.User == nil {
+			req.User = &openrtb2.User{}
 		}
-	}
-
-	if err := reqWrap.RebuildRequest(); err != nil {
-		return err
+		req.User.Consent = c.Consent
 	}
 
 	return nil
diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go
index 47f24bc9ecc..436f46dd563 100644
--- a/privacy/gdpr/consentwriter_test.go
+++ b/privacy/gdpr/consentwriter_test.go
@@ -27,14 +27,14 @@ func TestConsentWriter(t *testing.T) {
 			consent:     "anyConsent",
 			request:     &openrtb2.BidRequest{},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"anyConsent"}`)}},
+				Consent: "anyConsent"}},
 		},
 		{
 			description: "Enabled With Nil Request User Ext Object",
 			consent:     "anyConsent",
 			request:     &openrtb2.BidRequest{User: &openrtb2.User{}},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"anyConsent"}`)}},
+				Consent: "anyConsent"}},
 		},
 		{
 			description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite",
@@ -42,29 +42,25 @@ func TestConsentWriter(t *testing.T) {
 			request: &openrtb2.BidRequest{User: &openrtb2.User{
 				Ext: json.RawMessage(`{"existing":"any"}`)}},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}},
+				Consent: "anyConsent",
+				Ext:     json.RawMessage(`{"existing":"any"}`)}},
 		},
 		{
 			description: "Enabled With Existing Request User Ext Object - Overwrites",
 			consent:     "anyConsent",
 			request: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}},
+				Consent: "toBeOverwritten",
+				Ext:     json.RawMessage(`{"existing":"any"}`)}},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}},
-		},
-		{
-			description: "Enabled With Existing Malformed Request User Ext Object",
-			consent:     "anyConsent",
-			request: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`malformed`)}},
-			expectedError: true,
+				Consent: "anyConsent",
+				Ext:     json.RawMessage(`{"existing":"any"}`)}},
 		},
 		{
 			description: "Injection Attack With Nil Request User Object",
 			consent:     "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
 			request:     &openrtb2.BidRequest{},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`),
+				Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
 			}},
 		},
 		{
@@ -72,7 +68,8 @@ func TestConsentWriter(t *testing.T) {
 			consent:     "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
 			request:     &openrtb2.BidRequest{User: &openrtb2.User{}},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`),
+				Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
+				Ext:     nil,
 			}},
 		},
 		{
@@ -82,7 +79,8 @@ func TestConsentWriter(t *testing.T) {
 				Ext: json.RawMessage(`{"existing":"any"}`),
 			}},
 			expected: &openrtb2.BidRequest{User: &openrtb2.User{
-				Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`),
+				Consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"",
+				Ext:     json.RawMessage(`{"existing":"any"}`),
 			}},
 		},
 	}
diff --git a/sample/001_banner/pbjs.html b/sample/001_banner/pbjs.html
new file mode 100644
index 00000000000..af6ad643eec
--- /dev/null
+++ b/sample/001_banner/pbjs.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        div.adslot {
+            border-color: red;
+            border-style: solid;
+            width: 300px;
+            height: 600px;
+        }
+        p.description {
+            border-style: solid;
+        }
+    </style>
+    <script src="//cdn.jsdelivr.net/npm/prebid.js@latest/dist/not-for-prod/prebid.js" defer></script>
+    <script defer>
+
+        var sizes = [
+            [300, 250],
+            [300, 600]
+        ];
+
+        // use the cool "Prebid Less" feature https://docs.prebid.org/dev-docs/adunit-reference.html#stored-imp
+        var adUnits = [
+            {
+                code: 'test-div-1',
+                mediaTypes: {
+                    banner: {
+                        sizes: sizes
+                    }
+                },
+	        bids: [{
+	            module: "pbsBidAdapter",
+	            ortb2Imp: {
+		      ext: { prebid: { storedrequest: { id: 'test-imp-id' }}}
+                    }
+                }]
+            }
+        ]
+
+        function renderAllAdUnits() {
+            var winners = pbjs.getHighestCpmBids();
+            for (var i = 0; i < winners.length; i++) {
+                renderOne(winners[i]);
+            }
+        }
+
+        // create an iframe in the div and write the winning ad into it
+        function renderOne(winningBid) {
+            if (winningBid && winningBid.adId) {
+                var div = document.getElementById(winningBid.adUnitCode);
+                if (div) {
+                    const iframe = document.createElement('iframe');
+                    iframe.scrolling = 'no';
+                    iframe.frameBorder = '0';
+                    iframe.marginHeight = '0';
+                    iframe.marginHeight = '0';
+                    iframe.name = `prebid_ads_iframe_${winningBid.adUnitCode}`;
+                    iframe.title = '3rd party ad content';
+                    iframe.sandbox.add(
+                        'allow-forms',
+                        'allow-popups',
+                        'allow-popups-to-escape-sandbox',
+                        'allow-same-origin',
+                        'allow-scripts',
+                        'allow-top-navigation-by-user-activation'
+                    );
+                    iframe.setAttribute('aria-label', 'Advertisment');
+                    iframe.style.setProperty('border', '0');
+                    iframe.style.setProperty('margin', '0');
+                    iframe.style.setProperty('overflow', 'hidden');
+                    div.appendChild(iframe);
+                    const iframeDoc = iframe.contentWindow.document;
+                    pbjs.renderAd(iframeDoc, winningBid.adId);
+                }
+            }
+        }
+
+    </script>
+    <script defer>
+        var pbjs = pbjs || {};
+        pbjs.que = pbjs.que || [];
+
+        pbjs.que.push(function () {
+            pbjs.setConfig({
+                s2sConfig: [{
+                    accountId: '1',
+                    adapter: "prebidServer",
+                    enabled: true,
+                    endpoint: {
+                        noP1Consent: "http://localhost:8000/openrtb2/auction",
+                        p1Consent: 'http://localhost:8000/openrtb2/auction',
+                    },
+                    timeout: 1000,
+                    debug: true,
+                    allowUnknownBidderCodes: true
+                }],
+            })
+
+            pbjs.addAdUnits(adUnits);
+            pbjs.requestBids({
+                bidsBackHandler: renderAllAdUnits
+            })
+        });
+    </script>
+
+</head>
+
+<body>
+    <h1>001_banner</h1>
+    <p class="description">
+        This demo uses Prebid.js to interact with Prebid Server to fill the ad slot <strong>test-div-1</strong>
+        The auction request to Prebid Server uses a stored request, which in turn links to a stored response.</br>
+        Look for the <strong>/auction</strong> request in your browser's developer tool to inspect the request
+        and response.
+    </p>
+    <h2>&#8595I am ad unit test-div-1 &#8595</h2>
+        <div id="test-div-1">
+        </div>
+</body>
+</html>
diff --git a/schain/schainwriter.go b/schain/schainwriter.go
index b7ff0b52e95..141987ff4c5 100644
--- a/schain/schainwriter.go
+++ b/schain/schainwriter.go
@@ -3,7 +3,6 @@ package schain
 import (
 	"github.com/prebid/openrtb/v20/openrtb2"
 	"github.com/prebid/prebid-server/v2/openrtb_ext"
-	"github.com/prebid/prebid-server/v2/util/jsonutil"
 )
 
 // NewSChainWriter creates an ORTB 2.5 schain writer instance
@@ -34,9 +33,9 @@ type SChainWriter struct {
 // Write selects an schain from the multi-schain ORTB 2.5 location (req.ext.prebid.schains) for the specified bidder
 // and copies it to the ORTB 2.5 location (req.source.ext). If no schain exists for the bidder in the multi-schain
 // location and no wildcard schain exists, the request is not modified.
-func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) {
+func (w SChainWriter) Write(reqWrapper *openrtb_ext.RequestWrapper, bidder string) {
 	const sChainWildCard = "*"
-	var selectedSChain *openrtb2.SupplyChain
+	var selectedSChain openrtb2.SupplyChain
 
 	wildCardSChain := w.sChainsByBidder[sChainWildCard]
 	bidderSChain := w.sChainsByBidder[bidder]
@@ -46,32 +45,27 @@ func (w SChainWriter) Write(req *openrtb2.BidRequest, bidder string) {
 		return
 	}
 
-	selectedSChain = &openrtb2.SupplyChain{Ver: "1.0"}
+	selectedSChain = openrtb2.SupplyChain{Ver: "1.0"}
 
 	if bidderSChain != nil {
-		selectedSChain = bidderSChain
+		selectedSChain = *bidderSChain
 	} else if wildCardSChain != nil {
-		selectedSChain = wildCardSChain
+		selectedSChain = *wildCardSChain
 	}
 
-	schain := openrtb_ext.ExtRequestPrebidSChain{
-		SChain: *selectedSChain,
-	}
-
-	if req.Source == nil {
-		req.Source = &openrtb2.Source{}
+	if reqWrapper.Source == nil {
+		reqWrapper.Source = &openrtb2.Source{}
 	} else {
-		sourceCopy := *req.Source
-		req.Source = &sourceCopy
+		// Copy Source to avoid shared memory issues.
+		// Source may be modified differently for different bidders in request
+		sourceCopy := *reqWrapper.Source
+		reqWrapper.Source = &sourceCopy
 	}
 
-	if w.hostSChainNode != nil {
-		schain.SChain.Nodes = append(schain.SChain.Nodes, *w.hostSChainNode)
-	}
+	reqWrapper.Source.SChain = &selectedSChain
 
-	sourceExt, err := jsonutil.Marshal(schain)
-	if err == nil {
-		req.Source.Ext = sourceExt
+	if w.hostSChainNode != nil {
+		reqWrapper.Source.SChain.Nodes = append(reqWrapper.Source.SChain.Nodes, *w.hostSChainNode)
 	}
 }
 
diff --git a/schain/schainwriter_test.go b/schain/schainwriter_test.go
index d9b1358ffe6..757fc616052 100644
--- a/schain/schainwriter_test.go
+++ b/schain/schainwriter_test.go
@@ -17,206 +17,348 @@ func TestSChainWriter(t *testing.T) {
 	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
 	const seller3SChain string = `"schain":{"complete":3,"nodes":[{"asi":"directseller3.com","sid":"00003","rid":"BidRequest3","hp":3}],"ver":"3.0"}`
 	const sellerWildCardSChain string = `"schain":{"complete":1,"nodes":[{"asi":"wildcard1.com","sid":"wildcard1","rid":"WildcardReq1","hp":1}],"ver":"1.0"}`
-	const hostNode string = `{"asi":"pbshostcompany.com","sid":"00001","rid":"BidRequest","hp":1}`
 	const seller1Node string = `{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}`
 
 	tests := []struct {
 		description    string
-		giveRequest    openrtb2.BidRequest
+		giveRequest    *openrtb_ext.RequestWrapper
 		giveBidder     string
 		giveHostSChain *openrtb2.SupplyChainNode
-		wantRequest    openrtb2.BidRequest
+		wantRequest    *openrtb_ext.RequestWrapper
 		wantError      bool
 	}{
 		{
 			description: "nil source, nil ext.prebid.schains and empty host schain",
-			giveRequest: openrtb2.BidRequest{
-				Ext:    nil,
-				Source: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext:    nil,
+					Source: nil,
+				},
 			},
+
 			giveBidder:     "appnexus",
 			giveHostSChain: nil,
-			wantRequest: openrtb2.BidRequest{
-				Ext:    nil,
-				Source: nil,
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext:    nil,
+					Source: nil,
+				},
 			},
 		},
 		{
-			description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+			description: "Use source schain -- no bidder schain or wildcard schain in nil ext.prebid.schains - so source.schain is set and unmodified",
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Ver: "1.1",
+						},
+					},
 				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Ver: "1.1",
+						},
+					},
 				},
 			},
 		},
 		{
-			description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+			description: "Use source schain -- no bidder schain or wildcard schain in not nil ext.prebid.schains - so source.schain is set and unmodified",
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Ver: "1.1",
+						},
+						Ext: json.RawMessage(`{"some":"data"}`),
+					},
 				},
 			},
 			giveBidder: "rubicon",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller2SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Ver: "1.1",
+						},
+						Ext: json.RawMessage(`{"some":"data"}`),
+					},
 				},
 			},
 		},
 		{
-			description: "Use schain for bidder in ext.prebid.schains; ensure other ext.source field values are retained.",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					FD:     openrtb2.Int8Ptr(1),
-					TID:    "tid data",
-					PChain: "pchain data",
-					Ext:    json.RawMessage(`{` + seller2SChain + `}`),
+			description: "Use schain for bidder in ext.prebid.schains; ensure other source field values are retained.",
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						FD:     openrtb2.Int8Ptr(1),
+						TID:    "tid data",
+						PChain: "pchain data",
+						Ext:    json.RawMessage(`{"some":"data"}`),
+					},
 				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					FD:     openrtb2.Int8Ptr(1),
-					TID:    "tid data",
-					PChain: "pchain data",
-					Ext:    json.RawMessage(`{` + seller1SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						FD:     openrtb2.Int8Ptr(1),
+						TID:    "tid data",
+						PChain: "pchain data",
+						SChain: &openrtb2.SupplyChain{
+							Complete: 1,
+							Ver:      "1.0",
+							Ext:      nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "directseller1.com",
+									SID: "00001",
+									RID: "BidRequest1",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: json.RawMessage(`{"some":"data"}`),
+					},
 				},
 			},
 		},
 		{
 			description: "Use schain for bidder in ext.prebid.schains, nil req.source ",
-			giveRequest: openrtb2.BidRequest{
-				Ext:    json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext:    json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: nil,
+				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Complete: 1,
+							Ver:      "1.0",
+							Ext:      nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "directseller1.com",
+									SID: "00001",
+									RID: "BidRequest1",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: nil,
+					},
 				},
 			},
 		},
 		{
 			description: "Use wildcard schain in ext.prebid.schains.",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+					Source: &openrtb2.Source{
+						Ext: nil,
+					},
 				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + sellerWildCardSChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Complete: 1,
+							Ver:      "1.0",
+							Ext:      nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "wildcard1.com",
+									SID: "wildcard1",
+									RID: "WildcardReq1",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: nil,
+					},
 				},
 			},
 		},
 		{
 			description: "Use schain for bidder in ext.prebid.schains instead of wildcard.",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+					Source: &openrtb2.Source{
+						Ext: nil,
+					},
 				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller1SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["*"],` + sellerWildCardSChain + `}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Complete: 1,
+							Ver:      "1.0",
+							Ext:      nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "directseller1.com",
+									SID: "00001",
+									RID: "BidRequest1",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: nil,
+					},
 				},
 			},
 		},
 		{
 			description: "Use source schain -- multiple (two) bidder schains in ext.prebid.schains.",
-			giveRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller3SChain + `}`),
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						Ext: json.RawMessage(`{` + seller3SChain + `}`),
+					},
 				},
 			},
 			giveBidder: "appnexus",
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{` + seller3SChain + `}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`),
+					Source: &openrtb2.Source{
+						Ext: json.RawMessage(`{` + seller3SChain + `}`),
+					},
 				},
 			},
 			wantError: true,
 		},
 		{
 			description: "Schain in request, host schain defined, source.ext for bidder request should update with appended host schain",
-			giveRequest: openrtb2.BidRequest{
-				Ext:    json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`),
-				Source: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext:    json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`),
+					Source: nil,
+				},
 			},
 			giveBidder: "testbidder",
 			giveHostSChain: &openrtb2.SupplyChainNode{
 				ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1),
 			},
-			wantRequest: openrtb2.BidRequest{
-				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`),
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{"schain":{"complete":1,"nodes":[` + seller1Node + `,` + hostNode + `],"ver":"1.0"}}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["testbidder"],"schain":{"complete":1,"nodes":[` + seller1Node + `],"ver":"1.0"}}]}}`),
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Complete: 1,
+							Ver:      "1.0",
+							Ext:      nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "directseller1.com",
+									SID: "00001",
+									RID: "BidRequest1",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+								{
+									ASI: "pbshostcompany.com",
+									SID: "00001",
+									RID: "BidRequest",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: nil,
+					},
 				},
 			},
 		},
 		{
 			description: "No Schain in request, host schain defined, source.ext for bidder request should have just the host schain",
-			giveRequest: openrtb2.BidRequest{
-				Ext:    nil,
-				Source: nil,
+			giveRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext:    nil,
+					Source: nil,
+				},
 			},
 			giveBidder: "testbidder",
 			giveHostSChain: &openrtb2.SupplyChainNode{
 				ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1),
 			},
-			wantRequest: openrtb2.BidRequest{
-				Ext: nil,
-				Source: &openrtb2.Source{
-					Ext: json.RawMessage(`{"schain":{"complete":0,"nodes":[` + hostNode + `],"ver":"1.0"}}`),
+			wantRequest: &openrtb_ext.RequestWrapper{
+				BidRequest: &openrtb2.BidRequest{
+					Ext: nil,
+					Source: &openrtb2.Source{
+						SChain: &openrtb2.SupplyChain{
+							Ver: "1.0",
+							Ext: nil,
+							Nodes: []openrtb2.SupplyChainNode{
+								{
+									ASI: "pbshostcompany.com",
+									SID: "00001",
+									RID: "BidRequest",
+									HP:  openrtb2.Int8Ptr(1),
+									Ext: nil,
+								},
+							},
+						},
+						Ext: nil,
+					},
 				},
 			},
 		},
 	}
 
 	for _, tt := range tests {
-		// unmarshal ext to get schains object needed to initialize writer
-		var reqExt *openrtb_ext.ExtRequest
-		if tt.giveRequest.Ext != nil {
-			reqExt = &openrtb_ext.ExtRequest{}
-			err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt)
-			if err != nil {
-				t.Error("Unable to unmarshal request.ext")
+		t.Run(tt.description, func(t *testing.T) {
+			// unmarshal ext to get schains object needed to initialize writer
+			var reqExt *openrtb_ext.ExtRequest
+			if tt.giveRequest.Ext != nil {
+				reqExt = &openrtb_ext.ExtRequest{}
+				err := jsonutil.UnmarshalValid(tt.giveRequest.Ext, reqExt)
+				if err != nil {
+					t.Error("Unable to unmarshal request.ext")
+				}
 			}
-		}
 
-		writer, err := NewSChainWriter(reqExt, tt.giveHostSChain)
+			writer, err := NewSChainWriter(reqExt, tt.giveHostSChain)
 
-		if tt.wantError {
-			assert.NotNil(t, err)
-			assert.Nil(t, writer)
-		} else {
-			assert.Nil(t, err)
-			assert.NotNil(t, writer)
+			if tt.wantError {
+				assert.NotNil(t, err)
+				assert.Nil(t, writer)
+			} else {
+				assert.Nil(t, err)
+				assert.NotNil(t, writer)
 
-			writer.Write(&tt.giveRequest, tt.giveBidder)
+				writer.Write(tt.giveRequest, tt.giveBidder)
 
-			assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description)
-		}
+				assert.Equal(t, tt.wantRequest, tt.giveRequest, tt.description)
+			}
+		})
 	}
 }
diff --git a/static/bidder-info/adtonos.yaml b/static/bidder-info/adtonos.yaml
new file mode 100644
index 00000000000..37a81372710
--- /dev/null
+++ b/static/bidder-info/adtonos.yaml
@@ -0,0 +1,24 @@
+endpoint: https://exchange.adtonos.com/bid/{{.PublisherID}}
+maintainer:
+  email: support@adtonos.com
+gvlVendorID: 682
+geoscope:
+  - global
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - audio
+      # NOTE: This is purely an audio ad exchange - video ads are synthesized from audio ads for compatibility
+      # with mobile games that only understand video mimetypes. The visual layer is just a placeholder.
+      - video
+  site:
+    mediaTypes:
+      - audio
+  dooh:
+    mediaTypes:
+      - audio
+userSync:
+  redirect:
+    url: https://play.adtonos.com/redir?to={{.RedirectURL}}
+    userMacro: '@UUID@'
diff --git a/static/bidder-info/bidmatic.yaml b/static/bidder-info/bidmatic.yaml
new file mode 100644
index 00000000000..19211190033
--- /dev/null
+++ b/static/bidder-info/bidmatic.yaml
@@ -0,0 +1,18 @@
+endpoint: "http://adapter.bidmatic.io/pbs/ortb"
+maintainer:
+  email: "advertising@bidmatic.io"
+gvlVendorID: 1134
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+  # bidmatic supports user syncing, but requires configuration by the host. contact this
+  # bidder directly at the email address in this file to ask about enabling user sync.
+  supports:
+    - iframe
diff --git a/static/bidder-info/bluesea.yaml b/static/bidder-info/bluesea.yaml
index 14667cafd6e..a9a5ca203d6 100644
--- a/static/bidder-info/bluesea.yaml
+++ b/static/bidder-info/bluesea.yaml
@@ -3,9 +3,15 @@ maintainer:
   email: prebid@blueseasx.com
 endpointCompression: gzip
 modifyingVastXmlAllowed: true
+gvlVendorID: 1294
 capabilities:
   app:
     mediaTypes:
       - banner
       - native
       - video
+  site:
+    mediaTypes:
+      - banner
+      - native
+      - video
diff --git a/static/bidder-info/copper6ssp.yaml b/static/bidder-info/copper6ssp.yaml
new file mode 100644
index 00000000000..92dc5cfb7e8
--- /dev/null
+++ b/static/bidder-info/copper6ssp.yaml
@@ -0,0 +1,21 @@
+endpoint: "https://endpoint.copper6.com/"
+maintainer:
+  email: "info@copper6.com"
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
+userSync:
+  redirect:
+    url: "https://csync.copper6.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+    userMacro: "[UID]"
+  iframe:
+    url: "https://csync.copper6.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}"
+    userMacro: "[UID]"
diff --git a/static/bidder-info/escalax.yaml b/static/bidder-info/escalax.yaml
new file mode 100644
index 00000000000..8d6871182ce
--- /dev/null
+++ b/static/bidder-info/escalax.yaml
@@ -0,0 +1,18 @@
+endpoint: 'http://bidder_us.escalax.io/?partner={{.SourceId}}&token={{.AccountID}}&type=pbs'
+maintainer:
+  email: 'connect@escalax.io'
+geoscope:
+  - global
+endpointCompression: "GZIP"
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
diff --git a/static/bidder-info/freewheelssp.yaml b/static/bidder-info/freewheelssp.yaml
index 8c9286cbbc0..cd18c2d8172 100644
--- a/static/bidder-info/freewheelssp.yaml
+++ b/static/bidder-info/freewheelssp.yaml
@@ -13,4 +13,7 @@ capabilities:
 userSync:
   iframe:
     url: "https://ads.stickyadstv.com/pbs-user-sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&r={{.RedirectURL}}"
-    userMacro: "{viewerid}"
\ No newline at end of file
+    userMacro: "{viewerid}"
+openrtb:
+  version: 2.6
+  gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml
index f7e782e40df..945acf7ca5c 100644
--- a/static/bidder-info/gumgum.yaml
+++ b/static/bidder-info/gumgum.yaml
@@ -12,3 +12,6 @@ userSync:
     url: "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r={{.RedirectURL}}"
     userMacro: ""
     # gumgum appends the user id to end of the redirect url and does not utilize a macro
+openrtb:
+  version: 2.6
+  gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/kargo.yaml b/static/bidder-info/kargo.yaml
index 1a7a77eb8bb..6acd679ebc0 100644
--- a/static/bidder-info/kargo.yaml
+++ b/static/bidder-info/kargo.yaml
@@ -15,4 +15,5 @@ userSync:
     userMacro: "$UID"
 endpointCompression: "GZIP"
 openrtb:
+  version: 2.6
   gpp-supported: true
diff --git a/static/bidder-info/lemmadigital.yaml b/static/bidder-info/lemmadigital.yaml
index 535c91ffa77..03cabc8f710 100644
--- a/static/bidder-info/lemmadigital.yaml
+++ b/static/bidder-info/lemmadigital.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://sg.ads.lemmatechnologies.com/lemma/servad?pid={{.PublisherID}}&aid={{.AdUnit}}"
+endpoint: "https://pbid.lemmamedia.com/lemma/servad?src=prebid&pid={{.PublisherID}}&aid={{.AdUnit}}"
 maintainer:
   email: support@lemmatechnologies.com
 endpointCompression: gzip
@@ -11,4 +11,8 @@ capabilities:
   site:
     mediaTypes:
       - banner
-      - video
\ No newline at end of file
+      - video
+userSync:
+  iframe:
+    url: "https://sync.lemmadigital.com/setuid?publisher=850&redirect={{.RedirectURL}}"
+    userMacro: "${UUID}"
\ No newline at end of file
diff --git a/static/bidder-info/melozen.yaml b/static/bidder-info/melozen.yaml
new file mode 100644
index 00000000000..391e0a8d43b
--- /dev/null
+++ b/static/bidder-info/melozen.yaml
@@ -0,0 +1,19 @@
+# We have the following regional endpoint domains: us-east and us-west
+# Please deploy this config in each of your datacenters with the appropriate regional subdomain
+endpoint: "https://prebid.melozen.com/rtb/v2/bid?publisher_id={{.PublisherID}}"
+endpointCompression: gzip
+geoscope:
+  - global
+maintainer:
+  email: DSP@melodong.com
+capabilities:
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - native
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - native
\ No newline at end of file
diff --git a/static/bidder-info/missena.yaml b/static/bidder-info/missena.yaml
new file mode 100644
index 00000000000..47f089b9c5a
--- /dev/null
+++ b/static/bidder-info/missena.yaml
@@ -0,0 +1,16 @@
+endpoint: https://bid.missena.io/
+maintainer:
+  email: prebid@missena.com
+gvlVendorID: 687
+modifyingVastXmlAllowed: true
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+  site:
+    mediaTypes:
+      - banner
+userSync:
+  iframe:
+    url: https://sync.missena.io/iframe?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}
+    userMacro: $UID
\ No newline at end of file
diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml
index 1d6b323c3a6..62714f15124 100644
--- a/static/bidder-info/mobilefuse.yaml
+++ b/static/bidder-info/mobilefuse.yaml
@@ -13,3 +13,6 @@ capabilities:
       - video
       - native
 endpointCompression: "GZIP"
+openrtb:
+  version: 2.6
+  gpp-supported: true
diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml
index 9837b5dc92c..d001af72eb2 100644
--- a/static/bidder-info/openx.yaml
+++ b/static/bidder-info/openx.yaml
@@ -19,4 +19,6 @@ userSync:
   redirect:
     url: "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r={{.RedirectURL}}"
     userMacro: "${UID}"
+openrtb:
+  version: 2.6
 
diff --git a/static/bidder-info/oraki.yaml b/static/bidder-info/oraki.yaml
new file mode 100644
index 00000000000..d5e767ac540
--- /dev/null
+++ b/static/bidder-info/oraki.yaml
@@ -0,0 +1,18 @@
+endpoint: "https://eu1.oraki.io/pserver"
+maintainer:
+  email: "prebid@oraki.io"
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
+userSync:
+  redirect:
+    url: "https://sync.oraki.io/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+    userMacro: "[UID]"
diff --git a/static/bidder-info/ownadx.yaml b/static/bidder-info/ownadx.yaml
index 37567db1144..77120c81f23 100644
--- a/static/bidder-info/ownadx.yaml
+++ b/static/bidder-info/ownadx.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.AccountID}}/{{.ZoneID}}?token={{.SourceId}}"
+endpoint: "https://pbs.prebid-ownadx.com/bidder/bid/{{.SeatID}}/{{.SspID}}?token={{.TokenID}}"
 maintainer:
   email: prebid-team@techbravo.com
 capabilities:
diff --git a/static/bidder-info/playdigo.yaml b/static/bidder-info/playdigo.yaml
new file mode 100644
index 00000000000..b90b680f183
--- /dev/null
+++ b/static/bidder-info/playdigo.yaml
@@ -0,0 +1,24 @@
+endpoint: "https://server.playdigo.com/pserver"
+geoscope:
+  - USA
+maintainer:
+  email: "yr@playdigo.com"
+gvlVendorID: 1302
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
+userSync:
+  redirect:
+    url: "https://cs.playdigo.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+    userMacro: "[UID]"
+  iframe:
+    url: "https://cs.playdigo.com/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}"
+    userMacro: "[UID]"
diff --git a/static/bidder-info/pubrise.yaml b/static/bidder-info/pubrise.yaml
new file mode 100644
index 00000000000..fe5e6cd6d40
--- /dev/null
+++ b/static/bidder-info/pubrise.yaml
@@ -0,0 +1,21 @@
+endpoint: "https://backend.pubrise.ai/"
+maintainer:
+  email: "prebid@pubrise.ai"
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
+userSync:
+  redirect:
+    url: "https://sync.pubrise.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+    userMacro: "[UID]"
+  iframe:
+    url: "https://sync.pubrise.ai/pbserverIframe?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&pbserverUrl={{.RedirectURL}}"
+    userMacro: "[UID]"
diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml
index 762dbbb0c73..87aff0b5f04 100644
--- a/static/bidder-info/pulsepoint.yaml
+++ b/static/bidder-info/pulsepoint.yaml
@@ -19,3 +19,6 @@ userSync:
   redirect:
     url: "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl={{.RedirectURL}}"
     userMacro: "%%VGUID%%"
+openrtb:
+  version: 2.6
+  gpp-supported: true
diff --git a/static/bidder-info/qt.yaml b/static/bidder-info/qt.yaml
new file mode 100644
index 00000000000..a8d16e574bd
--- /dev/null
+++ b/static/bidder-info/qt.yaml
@@ -0,0 +1,19 @@
+endpoint: "https://endpoint1.qt.io/pserver"
+maintainer:
+  email: "qtssp-support@qt.io"
+gvlVendorID: 1331
+capabilities:
+  site:
+    mediaTypes:
+    - banner
+    - video
+    - native
+  app:
+    mediaTypes:
+    - banner
+    - video
+    - native
+userSync:
+  redirect:
+    url: "https://cs.qt.io/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+    userMacro: "[UID]"
diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml
index c3943058511..b4c2cfce6d2 100644
--- a/static/bidder-info/rubicon.yaml
+++ b/static/bidder-info/rubicon.yaml
@@ -13,6 +13,9 @@ xapi:
 maintainer:
   email: "header-bidding@rubiconproject.com"
 gvlVendorID: 52
+openrtb:
+  version: 2.6
+  gpp-supported: true
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-info/smartx.yaml b/static/bidder-info/smartx.yaml
index 9a387ecfbd2..a3c96a57528 100644
--- a/static/bidder-info/smartx.yaml
+++ b/static/bidder-info/smartx.yaml
@@ -1,4 +1,6 @@
 endpoint: "https://bid.smartclip.net/bid/1005"
+openrtb:
+  version: 2.6
 maintainer:
   email: "bidding@smartclip.tv"
 gvlVendorID: 115
diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml
index 6f9afc36b3f..dcbcbd2fda7 100644
--- a/static/bidder-info/sonobi.yaml
+++ b/static/bidder-info/sonobi.yaml
@@ -7,11 +7,16 @@ capabilities:
     mediaTypes:
       - banner
       - video
+      - native
   app:
     mediaTypes:
       - banner
       - video
+      - native
 userSync:
+  iframe:
+    url: "https://sync.go.sonobi.com/uc.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&loc={{.RedirectURL}}"
+    userMacro: "[UID]"
   redirect:
     url: "https://sync.go.sonobi.com/us.gif?loc={{.RedirectURL}}"
     userMacro: "[UID]"
diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml
index 62d74152b0b..fad1850d4b3 100644
--- a/static/bidder-info/sovrn.yaml
+++ b/static/bidder-info/sovrn.yaml
@@ -1,6 +1,7 @@
 endpoint: "http://pbs.lijit.com/rtb/bid?src=prebid_server"
 maintainer:
   email: "sovrnoss@sovrn.com"
+endpointCompression: gzip
 gvlVendorID: 13
 modifyingVastXmlAllowed: true
 capabilities:
diff --git a/static/bidder-info/streamlyn.yaml b/static/bidder-info/streamlyn.yaml
new file mode 100644
index 00000000000..0cf1444ef29
--- /dev/null
+++ b/static/bidder-info/streamlyn.yaml
@@ -0,0 +1,2 @@
+endpoint: "http://rtba.bidsxchange.com/openrtb/{{.PublisherID}}?host={{.Host}}"
+aliasOf: "limelightDigital"
diff --git a/static/bidder-info/tgm.yaml b/static/bidder-info/tgm.yaml
new file mode 100644
index 00000000000..29d2039ee3f
--- /dev/null
+++ b/static/bidder-info/tgm.yaml
@@ -0,0 +1 @@
+aliasOf: "limelightDigital"
diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml
index 605bcc71e6e..79a8951680f 100644
--- a/static/bidder-info/triplelift.yaml
+++ b/static/bidder-info/triplelift.yaml
@@ -12,9 +12,10 @@ capabilities:
       - banner
       - video
 userSync:
-  # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. 
-  # Contact this bidder directly at the email address above to ask about enabling user sync.
-  # 
+  # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted.
+  # If you are a publisher hosting your own Prebid Server, contact this bidder directly at the email address above to ask about enabling user sync.
+  # If you are a Prebid Server Host, please have your publisher contact the bidder.
+  #
   iframe:
     url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
     userMacro: $UID
@@ -23,4 +24,5 @@ userSync:
     userMacro: "$UID"
 endpointCompression: "GZIP"
 openrtb:
+  version: 2.6
   gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml
index ff93b544c4c..c5e08152692 100644
--- a/static/bidder-info/triplelift_native.yaml
+++ b/static/bidder-info/triplelift_native.yaml
@@ -12,8 +12,9 @@ capabilities:
     mediaTypes:
       - native
 userSync:
-  # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted. 
-  # Contact this bidder directly at the email address above to ask about enabling user sync.
+  # Triplelift supports user syncing but requires configuration by the host as the RedirectURL domain must be allowlisted.
+  # If you are a publisher hosting your own Prebid Server, contact this bidder directly at the email address above to ask about enabling user sync.
+  # If you are a Prebid Server Host, please have your publisher contact the bidder.
   # 
   iframe:
     url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
@@ -22,4 +23,5 @@ userSync:
     url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
     userMacro: "$UID"
 openrtb:
+  version: 2.6
   gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml
index 6389f831a64..62f03dec84d 100644
--- a/static/bidder-info/unruly.yaml
+++ b/static/bidder-info/unruly.yaml
@@ -1,7 +1,13 @@
 endpoint: "https://targeting.unrulymedia.com/unruly_prebid_server"
+endpointCompression: gzip
+geoscope:
+  - global
 maintainer:
   email: "prebidsupport@unrulygroup.com"
 gvlVendorID: 36
+openrtb:
+  version: 2.6
+  gpp-supported: true
 capabilities:
   app:
     mediaTypes:
@@ -14,4 +20,4 @@ capabilities:
 userSync:
   redirect:
     url: "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
-    userMacro: "[RX_UUID]"
\ No newline at end of file
+    userMacro: "[RX_UUID]"
diff --git a/static/bidder-info/vidazoo.yaml b/static/bidder-info/vidazoo.yaml
new file mode 100644
index 00000000000..a58f6849501
--- /dev/null
+++ b/static/bidder-info/vidazoo.yaml
@@ -0,0 +1,20 @@
+endpoint: "https://prebidsrvr.cootlogix.com/openrtb/"
+maintainer:
+  email: "dev@vidazoo.com"
+gvlVendorID: 744
+endpointCompression: gzip
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+  site:
+    mediaTypes:
+      - banner
+      - video
+userSync:
+  iframe:
+    url: https://sync.cootlogix.com/api/user/html/pbs_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}
+    userMacro: ${userId}
+openrtb:
+  gpp_supported: true
\ No newline at end of file
diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml
index 02ab8721a16..17b25ecbbf1 100644
--- a/static/bidder-info/yieldmo.yaml
+++ b/static/bidder-info/yieldmo.yaml
@@ -2,6 +2,8 @@ endpoint: "https://ads.yieldmo.com/exchange/prebid-server"
 maintainer:
   email: "prebid@yieldmo.com"
 gvlVendorID: 173
+openrtb:
+  version: 2.6
 capabilities:
   app:
     mediaTypes:
diff --git a/static/bidder-params/adtonos.json b/static/bidder-params/adtonos.json
new file mode 100644
index 00000000000..e43d23e2b4b
--- /dev/null
+++ b/static/bidder-params/adtonos.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "AdTonos Adapter Params",
+  "description": "A schema which validates params accepted by the AdTonos adapter",
+  
+  "type": "object",
+  "properties": {
+    "supplierId": {
+      "type": "string",
+      "description": "ID of the supplier account in AdTonos platform"
+    }
+  },
+  "required": ["supplierId"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/bidmatic.json b/static/bidder-params/bidmatic.json
new file mode 100644
index 00000000000..b3002a55ac5
--- /dev/null
+++ b/static/bidder-params/bidmatic.json
@@ -0,0 +1,29 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Bidmatic Adapter Params",
+  "description": "A schema which validates params accepted by the Bidmatic adapter",
+
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "integer",
+      "description": "An ID which identifies this placement of the impression"
+    },
+    "siteId": {
+      "type": "integer",
+      "description": "An ID which identifies the site selling the impression"
+    },
+    "source": {
+      "type": [
+        "integer",
+        "string"
+      ],
+      "description": "An ID which identifies the channel"
+    },
+    "bidFloor": {
+      "type": "number",
+      "description": "BidFloor, US Dollars"
+    }
+  },
+  "required": ["source"]
+}
diff --git a/static/bidder-params/copper6ssp.json b/static/bidder-params/copper6ssp.json
new file mode 100644
index 00000000000..9467e48e9b9
--- /dev/null
+++ b/static/bidder-params/copper6ssp.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Copper6SSPs Adapter Params",
+  "description": "A schema which validates params accepted by the Copper6SSP adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Placement ID"
+    },
+    "endpointId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Endpoint ID"
+    }
+  },
+  "oneOf": [
+    { "required": ["placementId"] },
+    { "required": ["endpointId"] }
+  ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/escalax.json b/static/bidder-params/escalax.json
new file mode 100644
index 00000000000..3045e7f1898
--- /dev/null
+++ b/static/bidder-params/escalax.json
@@ -0,0 +1,22 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "Escalax Adapter Params",
+    "description": "A schema which validates params accepted by the Escalax adapter",
+    "type": "object",
+    "properties": {
+        "accountId": {
+            "type": "string",
+            "description": "Account id",
+            "minLength": 1
+        },
+        "sourceId": {
+            "type": "string",
+            "description": "Source id",
+            "minLength": 1
+        }
+    },
+    "required": [
+        "accountId",
+        "sourceId"
+    ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/improvedigital.json b/static/bidder-params/improvedigital.json
index 55412c2f513..7c44faf3bed 100644
--- a/static/bidder-params/improvedigital.json
+++ b/static/bidder-params/improvedigital.json
@@ -7,16 +7,12 @@
         "placementId": {
             "type": "integer",
             "minimum": 1,
-            "description": "An ID which identifies this placement of the impression"
+            "description": "The placement ID from Improve Digital"
         },
         "publisherId": {
             "type": "integer",
             "minimum": 1,
-            "description": "An ID which identifies publisher. Required when using a placementKey"
-        },
-        "placementKey": {
-            "type": "string",
-            "description": "An uniq name which identifies this placement of the impression. Must be used with publisherId"
+            "description": "The publisher ID from Improve Digital"
         },
         "keyValues": {
             "type": "object",
@@ -32,13 +28,12 @@
                     "type": "integer"
                 }
             },
-            "required": ["w", "h"],
+            "required": [
+                "w",
+                "h"
+            ],
             "description": "Placement size"
         }
     },
-    "oneOf": [{
-        "required": ["placementId"]
-    }, {
-        "required": ["publisherId", "placementKey"]
-    }]
-}
+    "required": ["placementId"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/melozen.json b/static/bidder-params/melozen.json
new file mode 100644
index 00000000000..6b5cef5b3fd
--- /dev/null
+++ b/static/bidder-params/melozen.json
@@ -0,0 +1,14 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "MeloZen Adapter Params",
+  "description": "A schema which validates params accepted by the MeloZen adapter",
+  "type": "object",
+  "properties": {
+    "pubId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "The unique identifier for the publisher."
+    }
+  },
+  "required": ["pubId"]
+}
diff --git a/static/bidder-params/missena.json b/static/bidder-params/missena.json
new file mode 100644
index 00000000000..c9e20e5a828
--- /dev/null
+++ b/static/bidder-params/missena.json
@@ -0,0 +1,24 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Missena Adapter Params",
+  "description": "A schema which validates params accepted by the Missena adapter",
+  "type": "object",
+  "properties": {
+    "apiKey": {
+      "type": "string",
+      "description": "API Key",
+      "minLength": 1
+    },
+    "placement": {
+      "type": "string",
+      "description": "Placement Type (Sticky, Header, ...)"
+    },
+    "test": {
+      "type": "string",
+      "description": "Test Mode"
+    }
+  },
+  "required": [
+    "apiKey"
+  ]
+}
\ No newline at end of file
diff --git a/static/bidder-params/oraki.json b/static/bidder-params/oraki.json
new file mode 100644
index 00000000000..610c1f0e8c7
--- /dev/null
+++ b/static/bidder-params/oraki.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Oraki Adapter Params",
+  "description": "A schema which validates params accepted by the Oraki adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Placement ID"
+    },
+    "endpointId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Endpoint ID"
+    }
+  },
+  "oneOf": [
+    { "required": ["placementId"] },
+    { "required": ["endpointId"] }
+  ]
+}
diff --git a/static/bidder-params/ownadx.json b/static/bidder-params/ownadx.json
index f529e74cb01..e0e09a7e9f7 100644
--- a/static/bidder-params/ownadx.json
+++ b/static/bidder-params/ownadx.json
@@ -18,10 +18,5 @@
       "description": "Token ID"
     }
   },
-
-  "oneOf": [
-    { "required": ["sspId"] },
-    { "required": ["feedId"] },
-    { "required": ["token"] }
-  ]
+  "required": ["sspId","seatId","tokenId"]
 }
diff --git a/static/bidder-params/pubrise.json b/static/bidder-params/pubrise.json
new file mode 100644
index 00000000000..0d972da45e9
--- /dev/null
+++ b/static/bidder-params/pubrise.json
@@ -0,0 +1,22 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Pubrise Adapter Params",
+  "description": "A schema which validates params accepted by the Pubrise adapter",
+  "type": "object",
+  "properties": {
+    "placementId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Placement ID"
+    },
+    "endpointId": {
+      "type": "string",
+      "minLength": 1,
+      "description": "Endpoint ID"
+    }
+  },
+  "oneOf": [
+    { "required": ["placementId"] },
+    { "required": ["endpointId"] }
+  ]
+}
\ No newline at end of file
diff --git a/stored_requests/data/by_id/accounts/test.json b/stored_requests/data/by_id/accounts/test.json
index 699f6bd1e57..a53f8997f37 100644
--- a/stored_requests/data/by_id/accounts/test.json
+++ b/stored_requests/data/by_id/accounts/test.json
@@ -49,4 +49,4 @@
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/stored_responses/stored_responses.go b/stored_responses/stored_responses.go
index 04dc3decf5a..f7fb79f1c21 100644
--- a/stored_responses/stored_responses.go
+++ b/stored_responses/stored_responses.go
@@ -22,22 +22,9 @@ type ImpBidderReplaceImpID map[string]map[string]bool
 type BidderImpReplaceImpID map[string]map[string]bool
 
 func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
-	removeImpsWithStoredResponses(req, storedBidResponses)
 	return buildStoredResp(storedBidResponses)
 }
 
-// removeImpsWithStoredResponses deletes imps with stored bid resp
-func removeImpsWithStoredResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) {
-	imps := req.Imp
-	req.Imp = nil //to indicate this bidder doesn't have real requests
-	for _, imp := range imps {
-		if _, ok := storedBidResponses[imp.ID]; !ok {
-			//add real imp back to request
-			req.Imp = append(req.Imp, imp)
-		}
-	}
-}
-
 func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
 	// bidder -> imp id -> stored bid resp
 	bidderToImpToResponses := BidderImpsWithBidResponses{}
diff --git a/stored_responses/stored_responses_test.go b/stored_responses/stored_responses_test.go
index 49ce19d3414..99a7bf5ae44 100644
--- a/stored_responses/stored_responses_test.go
+++ b/stored_responses/stored_responses_test.go
@@ -11,72 +11,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestRemoveImpsWithStoredResponses(t *testing.T) {
-	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
-	testCases := []struct {
-		description        string
-		reqIn              *openrtb2.BidRequest
-		storedBidResponses ImpBidderStoredResp
-		expectedImps       []openrtb2.Imp
-	}{
-		{
-			description: "request with imps and stored bid response for this imp",
-			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
-				{ID: "imp-id1"},
-			}},
-			storedBidResponses: ImpBidderStoredResp{
-				"imp-id1": {"appnexus": bidRespId1},
-			},
-			expectedImps: nil,
-		},
-		{
-			description: "request with imps and stored bid response for one of these imp",
-			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
-				{ID: "imp-id1"},
-				{ID: "imp-id2"},
-			}},
-			storedBidResponses: ImpBidderStoredResp{
-				"imp-id1": {"appnexus": bidRespId1},
-			},
-			expectedImps: []openrtb2.Imp{
-				{
-					ID: "imp-id2",
-				},
-			},
-		},
-		{
-			description: "request with imps and stored bid response for both of these imp",
-			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
-				{ID: "imp-id1"},
-				{ID: "imp-id2"},
-			}},
-			storedBidResponses: ImpBidderStoredResp{
-				"imp-id1": {"appnexus": bidRespId1},
-				"imp-id2": {"appnexus": bidRespId1},
-			},
-			expectedImps: nil,
-		},
-		{
-			description: "request with imps and no stored bid responses",
-			reqIn: &openrtb2.BidRequest{Imp: []openrtb2.Imp{
-				{ID: "imp-id1"},
-				{ID: "imp-id2"},
-			}},
-			storedBidResponses: nil,
-
-			expectedImps: []openrtb2.Imp{
-				{ID: "imp-id1"},
-				{ID: "imp-id2"},
-			},
-		},
-	}
-	for _, testCase := range testCases {
-		request := testCase.reqIn
-		removeImpsWithStoredResponses(request, testCase.storedBidResponses)
-		assert.Equal(t, testCase.expectedImps, request.Imp, "incorrect Impressions for testCase %s", testCase.description)
-	}
-}
-
 func TestBuildStoredBidResponses(t *testing.T) {
 	bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`)
 	bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`)
diff --git a/usersync/chooser.go b/usersync/chooser.go
index d8bf731f693..462ed635e5a 100644
--- a/usersync/chooser.go
+++ b/usersync/chooser.go
@@ -86,8 +86,8 @@ const (
 	// StatusUnknownBidder specifies a requested bidder is unknown to Prebid Server.
 	StatusUnknownBidder
 
-	// StatusTypeNotSupported specifies a requested sync type is not supported by a specific bidder.
-	StatusTypeNotSupported
+	// StatusRejectedByFilter specifies a requested sync type is not supported by a specific bidder.
+	StatusRejectedByFilter
 
 	// StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice.
 	StatusDuplicate
@@ -181,7 +181,7 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}
 	syncersSeen[syncer.Key()] = struct{}{}
 
 	if !syncer.SupportsType(syncTypeFilter.ForBidder(strings.ToLower(bidder))) {
-		return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()}
+		return nil, BidderEvaluation{Status: StatusRejectedByFilter, Bidder: bidder, SyncerKey: syncer.Key()}
 	}
 
 	if cookie.HasLiveSync(syncer.Key()) {
diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go
index f48dbeff9f1..9e57f1da4ce 100644
--- a/usersync/chooser_test.go
+++ b/usersync/chooser_test.go
@@ -153,7 +153,7 @@ func TestChooserChoose(t *testing.T) {
 			bidderNamesLookup:  normalizedBidderNamesLookup,
 			expected: Result{
 				Status:           StatusOK,
-				BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}},
+				BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}},
 				SyncersChosen:    []SyncerChoice{},
 			},
 		},
@@ -228,7 +228,7 @@ func TestChooserChoose(t *testing.T) {
 			bidderNamesLookup:  normalizedBidderNamesLookup,
 			expected: Result{
 				Status:           StatusOK,
-				BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}},
+				BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}},
 				SyncersChosen:    []SyncerChoice{syncerChoiceA},
 			},
 		},
@@ -243,7 +243,7 @@ func TestChooserChoose(t *testing.T) {
 			bidderNamesLookup:  normalizedBidderNamesLookup,
 			expected: Result{
 				Status:           StatusOK,
-				BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}},
+				BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusRejectedByFilter}},
 				SyncersChosen:    []SyncerChoice{syncerChoiceA},
 			},
 		},
@@ -531,7 +531,7 @@ func TestChooserEvaluate(t *testing.T) {
 			givenSyncTypeFilter:         syncTypeFilter,
 			normalizedBidderNamesLookup: normalizedBidderNamesLookup,
 			expectedSyncer:              nil,
-			expectedEvaluation:          BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported},
+			expectedEvaluation:          BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusRejectedByFilter},
 		},
 		{
 			description:                 "Already Synced",