Skip to content
Open
76 changes: 76 additions & 0 deletions modules/synapsehxBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Overview

```
Module Name: Synapse HX Bidder Adapter
Module Type: Bidder Adapter
Maintainer: prebid@compas-inc.com
```

# Description

The Synapse HX Bidder Adapter enables publishers to integrate with Synapse HX exchange for banner and video ad formats. The adapter supports OpenRTB standards and processes bid requests efficiently using the Prebid.js framework.

# Test Parameters

## Sample Banner Ad Unit
```
var adUnits = [
{
code: 'test-div',
mediaTypes: {
banner: {
sizes: [[300,250]]
}
},
bids: [
{
bidder: 'synapsehx',
params: {
// REQUIRED - Synapse HX tenant identifier
tenantId: 'your-account-id',
// OPTIONAL - Synapse HX ad unit identifier
adUnitId: 'ad-unit-id'
}
}
]
}
];
```

## Sample Video Ad Unit
```
var videoAdUnits = [
{
code: 'test-div-video',
mediaTypes: {
video: {
context: 'instream',
placement: 1,
playerSize: [640, 360],
mimes: ['video/mp4'],
protocols: [2, 3, 5, 6],
api: [2],
maxduration: 30,
linearity: 1,
playbackmethod: [2]
}
},
bids: [
{
bidder: 'synapsehx',
params: {
Comment thread
patmmccann marked this conversation as resolved.
// REQUIRED - Synapse HX tenant identifier
tenantId: 'your-account-id',
// OPTIONAL - Synapse HX ad unit identifier
adUnitId: 'ad-unit-id'
}
}
]
}
];
```

# Additional Notes
- The adapter processes requests via OpenRTB 2.6 standards.
- Ensure that the `tenantId` parameter is set correctly for your integration.
- The `adUnitId` parameter is optional.
161 changes: 161 additions & 0 deletions modules/synapsehxBidAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { BidderSpec, registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { logError, formatQS, triggerPixel } from '../src/utils.js';
import { getBidFloor } from '../libraries/adrelevantisUtils/bidderUtils.js';
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { toOrtb26 } from '../libraries/ortb2.5Translator/translator.js';
import { getUserSyncParams } from '../libraries/userSyncUtils/userSyncUtils.js';

const BIDDER_CODE = 'synapsehx';
const METHOD = 'POST';
const ENDPOINT_URL = `https://rtb.hx.compasonline.com/pbjs`;

type SynapsehxBidderParams = {
/**
* Synapse HX tenant identifier
*/
tenantId: string;
/**
* Synapse HX ad unit identifier
*/
adUnitId?: string;
};

declare module '../src/adUnits' {
interface BidderParams {
[BIDDER_CODE]: SynapsehxBidderParams;
}
}

function getMediaType(bid) {
const mtypeToMediaType = { 1: BANNER, 2: VIDEO };
return mtypeToMediaType[bid.mtype];
}

const converter = ortbConverter<typeof BIDDER_CODE>({
imp(buildImp, bidRequest, context) {
const imp = buildImp(bidRequest, context);

if (bidRequest?.params?.adUnitId) {
imp.tagid = bidRequest.params.adUnitId;
}

const floor = getBidFloor(bidRequest, 'USD');
if (floor) {
imp.bidfloor = floor;
imp.bidfloorcur = 'USD';
}

return imp;
},
request(buildRequest, imps, bidderRequest, context) {
return buildRequest(imps, bidderRequest, context);
},
bidResponse(buildBidResponse, bid, context) {
const isValidBidType = Object.keys(context.bidRequest.mediaTypes).includes(getMediaType(bid));

if (isValidBidType) {
return buildBidResponse(bid, context);
}

logError('Incorrect bid type for bid: ', bid.id);
},
context: {
netRevenue: true,
ttl: 30
}
});

function isValidBidFloorCurrency(bid) {
const currency = bid.ortb2Imp?.bidfloorcur;
return currency == null || currency === 'USD';
}

function isValidParams(bid) {
const tenantId = bid?.params?.tenantId;
const adUnitId = bid?.params?.adUnitId;
return typeof tenantId === 'string' && tenantId.length > 0 &&
(adUnitId == null || (typeof adUnitId === 'string' && adUnitId.length > 0));
}

function makeUrl(bidRequests) {
return `${ENDPOINT_URL}?${formatQS({ pid: bidRequests[0].params.tenantId })}`;
Comment on lines +81 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Split batched requests by tenant

When a page has multiple Synapse HX bids with different tenantId values, bidderFactory passes them together to buildRequests, but this URL is built only from bidRequests[0] and the ORTB impressions do not carry the other tenants. Those later impressions are therefore sent under the first tenant's pid, causing misattribution or rejection for multi-tenant pages; split requests by tenantId or enforce a single tenant per request.

Useful? React with 👍 / 👎.

}

export const spec: BidderSpec<typeof BIDDER_CODE> = {
code: BIDDER_CODE,
supportedMediaTypes: [VIDEO, BANNER],
isBidRequestValid: (bid) => !!bid && isValidParams(bid) && isValidBidFloorCurrency(bid),

buildRequests: (bidRequests, bidderRequest) => {
const data = toOrtb26(converter.toORTB({ bidRequests, bidderRequest }));

return [{
method: METHOD,
url: makeUrl(bidRequests),
options: {
contentType: 'text/plain',
withCredentials: true,
crossOrigin: true
},
data: data,
}];
},

interpretResponse: ({ body }, req) => {
if (!body || !body.seatbid || body.seatbid.length === 0) {
return [];
}
body.cur ??= "USD";
return converter.fromORTB({
response: body,
request: req.data
});
},

onTimeout: (data) => { },

onBidWon: (bid: { nurl?: string, cpm: number }) => {
if (bid.nurl) {
const url = new URL(bid.nurl);
url.searchParams.set('cpm', String(bid.cpm));
triggerPixel(url.toString());
Comment on lines +119 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve win notice URLs before onBidWon

When Synapse returns a normal ORTB nurl (the fixture does this), converter.fromORTB does not leave that field on the Prebid bid; the default banner/video processors consume it into render assets instead. As a result this onBidWon branch never sees bid.nurl for bids produced by this adapter, so the intended win notice with the CPM is not sent on bid win. Preserve bid.nurl in the converter's bidResponse customizer (and avoid also consuming it there if necessary) before relying on this handler.

Useful? React with 👍 / 👎.

}
},

onSetTargeting: (bid) => { },

getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) {
const syncs = [];

if (serverResponses && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) {
const params = formatQS(getUserSyncParams(gdprConsent, uspConsent, gppConsent));

if (syncOptions.iframeEnabled) {
serverResponses.forEach(response => {
const iframeUrl = response?.body?.ext?.[BIDDER_CODE]?.sync?.iframe;
if (iframeUrl) {
syncs.push({
type: "iframe",
url: `${iframeUrl}${params ? `${iframeUrl.lastIndexOf('?') !== -1 ? '&' : '?'}${params}` : ''}`,
});
}
});
} else if (syncOptions.pixelEnabled) {
serverResponses.forEach(response => {
const images = response?.body?.ext?.[BIDDER_CODE]?.sync?.image || [];
images.forEach(image => {
syncs.push({
type: "image",
url: `${image}${params ? `${image.lastIndexOf('?') !== -1 ? '&' : '?'}${params}` : ''}`,
});
});
});
}
}

return syncs;
}
};

registerBidder(spec);
Loading