diff --git a/integrationExamples/gpt/mgidRtdProvider_example.html b/integrationExamples/gpt/mgidRtdProvider_example.html
new file mode 100644
index 00000000000..e3e4f720586
--- /dev/null
+++ b/integrationExamples/gpt/mgidRtdProvider_example.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+JS Bin
+
+
+
+Basic Prebid.js Example
+Div-1
+
+
+
+
+
+
+
diff --git a/modules/.submodules.json b/modules/.submodules.json
index e4d09b8c9df..aad778be67b 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -64,6 +64,7 @@
"iasRtdProvider",
"jwplayerRtdProvider",
"medianetRtdProvider",
+ "mgidRtdProvider",
"oneKeyRtdProvider",
"optimeraRtdProvider",
"permutiveRtdProvider",
diff --git a/modules/mgidRtdProvider.js b/modules/mgidRtdProvider.js
new file mode 100644
index 00000000000..f30f14ea528
--- /dev/null
+++ b/modules/mgidRtdProvider.js
@@ -0,0 +1,190 @@
+import { submodule } from '../src/hook.js';
+import {ajax} from '../src/ajax.js';
+import {deepAccess, logError, logInfo, mergeDeep} from '../src/utils.js';
+import {getStorageManager} from '../src/storageManager.js';
+import {getRefererInfo} from '../src/refererDetection.js';
+
+const MODULE_NAME = 'realTimeData';
+const SUBMODULE_NAME = 'mgid';
+const MGID_RTD_API_URL = 'https://servicer.mgid.com/sda';
+const MGUID_LOCAL_STORAGE_KEY = 'mguid';
+const ORTB2_NAME = 'www.mgid.com'
+
+const GVLID = 358;
+/** @type {?Object} */
+export const storage = getStorageManager({
+ gvlid: GVLID,
+ moduleName: SUBMODULE_NAME
+});
+
+function init(moduleConfig) {
+ if (!moduleConfig?.params?.clientSiteId) {
+ logError('Mgid clientSiteId is not set!');
+ return false;
+ }
+ return true;
+}
+
+function getBidRequestData(reqBidsConfigObj, onDone, moduleConfig, userConsent) {
+ let mguid;
+ try {
+ mguid = storage.getDataFromLocalStorage(MGUID_LOCAL_STORAGE_KEY);
+ } catch (e) {
+ logInfo(`Can't get mguid from localstorage`);
+ }
+
+ const params = [
+ {
+ name: 'gdprApplies',
+ data: typeof userConsent?.gdpr?.gdprApplies !== 'undefined' ? userConsent?.gdpr?.gdprApplies + '' : undefined,
+ },
+ {
+ name: 'consentData',
+ data: userConsent?.gdpr?.consentString,
+ },
+ {
+ name: 'uspString',
+ data: userConsent?.usp,
+ },
+ {
+ name: 'cxurl',
+ data: encodeURIComponent(getContextUrl()),
+ },
+ {
+ name: 'muid',
+ data: mguid,
+ },
+ {
+ name: 'clientSiteId',
+ data: moduleConfig?.params?.clientSiteId,
+ },
+ {
+ name: 'cxlang',
+ data: deepAccess(reqBidsConfigObj.ortb2Fragments.global, 'site.content.language'),
+ },
+ ];
+
+ const url = MGID_RTD_API_URL + '?' + params.filter((p) => p.data).map((p) => p.name + '=' + p.data).join('&');
+
+ let isDone = false;
+
+ ajax(url, {
+ success: (response, req) => {
+ if (req.status === 200) {
+ try {
+ const data = JSON.parse(response);
+ const ortb2 = reqBidsConfigObj?.ortb2Fragments?.global || {};
+
+ mergeDeep(ortb2, getDataForMerge(data));
+
+ if (data?.muid) {
+ try {
+ mguid = storage.setDataInLocalStorage(MGUID_LOCAL_STORAGE_KEY, data.muid);
+ } catch (e) {
+ logInfo(`Can't set mguid to localstorage`);
+ }
+ }
+
+ onDone();
+ isDone = true;
+ } catch (e) {
+ onDone();
+ isDone = true;
+
+ logError('Unable to parse Mgid RTD data', e);
+ }
+ } else {
+ onDone();
+ isDone = true;
+
+ logError('Mgid RTD wrong response status');
+ }
+ },
+ error: () => {
+ onDone();
+ isDone = true;
+
+ logError('Unable to get Mgid RTD data');
+ }
+ },
+ null, {
+ method: 'GET',
+ withCredentials: false,
+ });
+
+ setTimeout(function () {
+ if (!isDone) {
+ onDone();
+ logInfo('Mgid RTD timeout');
+ isDone = true;
+ }
+ }, moduleConfig.params.timeout || 1000);
+}
+
+function getContextUrl() {
+ const refererInfo = getRefererInfo();
+
+ let resultUrl = refererInfo.canonicalUrl || refererInfo.topmostLocation;
+
+ const metaElements = document.getElementsByTagName('meta');
+ for (let i = 0; i < metaElements.length; i++) {
+ if (metaElements[i].getAttribute('property') === 'og:url') {
+ resultUrl = metaElements[i].content;
+ }
+ }
+
+ return resultUrl;
+}
+
+function getDataForMerge(responseData) {
+ let siteData = {
+ name: ORTB2_NAME
+ };
+ let userData = {
+ name: ORTB2_NAME
+ };
+
+ if (responseData.siteSegments) {
+ siteData.segment = responseData.siteSegments.map((segmentId) => ({ id: segmentId }));
+ }
+ if (responseData.siteSegtax) {
+ siteData.ext = {
+ segtax: responseData.siteSegtax
+ }
+ }
+
+ if (responseData.userSegments) {
+ userData.segment = responseData.userSegments.map((segmentId) => ({ id: segmentId }));
+ }
+ if (responseData.userSegtax) {
+ userData.ext = {
+ segtax: responseData.userSegtax
+ }
+ }
+
+ let result = {};
+ if (siteData.segment || siteData.ext) {
+ result.site = {
+ content: {
+ data: [siteData],
+ }
+ }
+ }
+
+ if (userData.segment || userData.ext) {
+ result.user = {
+ data: [userData],
+ }
+ }
+
+ return result;
+}
+
+/** @type {RtdSubmodule} */
+export const mgidSubmodule = {
+ name: SUBMODULE_NAME,
+ init: init,
+ getBidRequestData: getBidRequestData,
+};
+
+submodule(MODULE_NAME, mgidSubmodule);
diff --git a/modules/mgidRtdProvider.md b/modules/mgidRtdProvider.md
new file mode 100644
index 00000000000..58d4564e14e
--- /dev/null
+++ b/modules/mgidRtdProvider.md
@@ -0,0 +1,51 @@
+# Overview
+
+```
+Module Name: Mgid RTD Provider
+Module Type: RTD Provider
+Maintainer: prebid@mgid.com
+```
+
+# Description
+
+Mgid RTD module allows you to enrich bid data with contextual and audience signals, based on IAB taxonomies.
+
+## Configuration
+
+This module is configured as part of the `realTimeData.dataProviders` object.
+
+{: .table .table-bordered .table-striped }
+| Name | Scope | Description | Example | Type |
+|------------|----------|----------------------------------------|---------------|----------|
+| `name ` | required | Real time data module name | `'mgid'` | `string` |
+| `params` | required | | | `Object` |
+| `params.clientSiteId` | required | The client site id provided by Mgid. | `'123456'` | `string` |
+| `params.timeout` | optional | Maximum amount of milliseconds allowed for module to finish working | `1000` | `number` |
+
+#### Example
+
+```javascript
+pbjs.setConfig({
+ realTimeData: {
+ dataProviders: [{
+ name: 'mgid',
+ params: {
+ clientSiteId: '123456'
+ }
+ }]
+ }
+});
+```
+
+## Integration
+To install the module, follow these instructions:
+
+#### Step 1: Prepare the base Prebid file
+
+- Option 1: Use Prebid [Download](/download.html) page to build the prebid package. Ensure that you do check *Mgid Realtime Module* module
+
+- Option 2: From the command line, run `gulp build --modules=mgidRtdProvider,...`
+
+#### Step 2: Set configuration
+
+Enable Mgid Real Time Module using `pbjs.setConfig`. Example is provided in Configuration section.
diff --git a/test/spec/modules/mgidRtdProvider_spec.js b/test/spec/modules/mgidRtdProvider_spec.js
new file mode 100644
index 00000000000..4f70b4d8b7c
--- /dev/null
+++ b/test/spec/modules/mgidRtdProvider_spec.js
@@ -0,0 +1,366 @@
+import { mgidSubmodule, storage } from '../../../modules/mgidRtdProvider.js';
+import {expect} from 'chai';
+import * as refererDetection from '../../../src/refererDetection';
+
+describe('Mgid RTD submodule', () => {
+ let server;
+ let clock;
+ let getRefererInfoStub;
+ let getDataFromLocalStorageStub;
+
+ beforeEach(() => {
+ server = sinon.fakeServer.create();
+
+ clock = sinon.useFakeTimers();
+
+ getRefererInfoStub = sinon.stub(refererDetection, 'getRefererInfo');
+ getRefererInfoStub.returns({
+ canonicalUrl: 'https://www.test.com/abc'
+ });
+
+ getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage').returns('qwerty654321');
+ });
+
+ afterEach(() => {
+ server.restore();
+ clock.restore();
+ getRefererInfoStub.restore();
+ getDataFromLocalStorageStub.restore();
+ });
+
+ it('init is successfull, when clientSiteId is defined', () => {
+ expect(mgidSubmodule.init({params: {clientSiteId: 123}})).to.be.true;
+ });
+
+ it('init is unsuccessfull, when clientSiteId is not defined', () => {
+ expect(mgidSubmodule.init({})).to.be.false;
+ });
+
+ it('getBidRequestData send all params to our endpoint and succesfully modifies ortb2', () => {
+ const responseObj = {
+ userSegments: ['100', '200'],
+ userSegtax: 5,
+ siteSegments: ['300', '400'],
+ siteSegtax: 7,
+ muid: 'qwerty654321',
+ };
+
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {
+ site: {
+ content: {
+ language: 'en',
+ }
+ }
+ },
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {
+ gdpr: {
+ gdprApplies: true,
+ consentString: 'testConsent',
+ },
+ usp: '1YYY',
+ }
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ JSON.stringify(responseObj)
+ );
+
+ const requestUrl = new URL(server.requests[0].url);
+ expect(requestUrl.host).to.be.eq('servicer.mgid.com');
+ expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('true');
+ expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent');
+ expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY');
+ expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321');
+ expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123');
+ expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc');
+ expect(requestUrl.searchParams.get('cxlang')).to.be.eq('en');
+
+ assert.deepInclude(
+ reqBidsConfigObj.ortb2Fragments.global,
+ {
+ site: {
+ content: {
+ language: 'en',
+ data: [
+ {
+ name: 'www.mgid.com',
+ ext: {
+ segtax: 7
+ },
+ segment: [
+ { id: '300' },
+ { id: '400' },
+ ]
+ }
+ ],
+ }
+ },
+ user: {
+ data: [
+ {
+ name: 'www.mgid.com',
+ ext: {
+ segtax: 5
+ },
+ segment: [
+ { id: '100' },
+ { id: '200' },
+ ]
+ }
+ ],
+ },
+ });
+ });
+
+ it('getBidRequestData doesn\'t send params (consent and cxlang), if we haven\'t received them', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ JSON.stringify({})
+ );
+
+ const requestUrl = new URL(server.requests[0].url);
+ expect(requestUrl.host).to.be.eq('servicer.mgid.com');
+ expect(requestUrl.searchParams.get('gdprApplies')).to.be.null;
+ expect(requestUrl.searchParams.get('consentData')).to.be.null;
+ expect(requestUrl.searchParams.get('uspString')).to.be.null;
+ expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321');
+ expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123');
+ expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc');
+ expect(requestUrl.searchParams.get('cxlang')).to.be.null;
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData send gdprApplies event if it is false', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {
+ gdpr: {
+ gdprApplies: false,
+ consentString: 'testConsent',
+ },
+ usp: '1YYY',
+ }
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ JSON.stringify({})
+ );
+
+ const requestUrl = new URL(server.requests[0].url);
+ expect(requestUrl.host).to.be.eq('servicer.mgid.com');
+ expect(requestUrl.searchParams.get('gdprApplies')).to.be.eq('false');
+ expect(requestUrl.searchParams.get('consentData')).to.be.eq('testConsent');
+ expect(requestUrl.searchParams.get('uspString')).to.be.eq('1YYY');
+ expect(requestUrl.searchParams.get('muid')).to.be.eq('qwerty654321');
+ expect(requestUrl.searchParams.get('clientSiteId')).to.be.eq('123');
+ expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/abc');
+ expect(requestUrl.searchParams.get('cxlang')).to.be.null;
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData use og:url for cxurl, if it is available', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ let metaStub = sinon.stub(document, 'getElementsByTagName').returns([
+ { getAttribute: () => 'og:test', content: 'fake' },
+ { getAttribute: () => 'og:url', content: 'https://realOgUrl.com/' }
+ ]);
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ JSON.stringify({})
+ );
+
+ const requestUrl = new URL(server.requests[0].url);
+ expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://realOgUrl.com/');
+ expect(onDone.calledOnce).to.be.true;
+
+ metaStub.restore();
+ });
+
+ it('getBidRequestData use topMostLocation for cxurl, if nothing else left', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ getRefererInfoStub.returns({
+ topmostLocation: 'https://www.test.com/topMost'
+ });
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ JSON.stringify({})
+ );
+
+ const requestUrl = new URL(server.requests[0].url);
+ expect(requestUrl.searchParams.get('cxurl')).to.be.eq('https://www.test.com/topMost');
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData won\'t modify ortb2 if response is broken', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 200,
+ {'Content-Type': 'application/json'},
+ '{'
+ );
+
+ assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {});
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData won\'t modify ortb2 if response status is not 200', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 204,
+ {'Content-Type': 'application/json'},
+ '{}'
+ );
+
+ assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {});
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData won\'t modify ortb2 if response results in error', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123}},
+ {}
+ );
+
+ server.requests[0].respond(
+ 500,
+ {'Content-Type': 'application/json'},
+ '{}'
+ );
+
+ assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {});
+ expect(onDone.calledOnce).to.be.true;
+ });
+
+ it('getBidRequestData won\'t modify ortb2 if response time hits timeout', () => {
+ let reqBidsConfigObj = {
+ ortb2Fragments: {
+ global: {},
+ }
+ };
+
+ let onDone = sinon.stub();
+
+ mgidSubmodule.getBidRequestData(
+ reqBidsConfigObj,
+ onDone,
+ {params: {clientSiteId: 123, timeout: 500}},
+ {}
+ );
+
+ clock.tick(510);
+
+ assert.deepEqual(reqBidsConfigObj.ortb2Fragments.global, {});
+ expect(onDone.calledOnce).to.be.true;
+ });
+});