-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New Bid Adapter - goadserver #14701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
New Bid Adapter - goadserver #14701
Changes from 1 commit
fc20fa2
f24cd10
51ca38d
f8456ef
76e35e9
f0ee188
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import { ortbConverter } from '../libraries/ortbConverter/converter.js'; | ||
| import { registerBidder } from '../src/adapters/bidderFactory.js'; | ||
| import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; | ||
| import { deepSetValue, triggerPixel, isStr } from '../src/utils.js'; | ||
|
|
||
| /** | ||
| * Prebid.js adapter for goadserver — a self-hosted, multi-tenant ad | ||
| * serving platform with OpenRTB 2.5 Prebid Server support. | ||
| * | ||
| * Each goadserver deployment runs under its own domain and exposes the | ||
| * standard `/openrtb2/auction` endpoint. Publishers point the adapter at | ||
| * their specific deployment via `params.host`; the authentication token | ||
| * (the SSP campaign hash from the publisher's goadserver panel) is | ||
| * passed via `params.token` and lands in the outgoing BidRequest as | ||
| * `site.publisher.id` — the location goadserver's auction handler | ||
| * resolves the publisher account from. | ||
| * | ||
| * One adapter serves every goadserver deployment. There is deliberately | ||
| * no bidder-code alias per deployment because the endpoint URL and token | ||
| * are per-bid parameters, not per-registration; publishers running | ||
| * multiple goadserver instances just pass different `params.host` values. | ||
| * | ||
| * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid | ||
| * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest | ||
| */ | ||
|
|
||
| const BIDDER_CODE = 'goadserver'; | ||
| const DEFAULT_CURRENCY = 'USD'; | ||
| const DEFAULT_TTL = 300; | ||
| // GVL ID: not yet registered with IAB Europe. File a TCF registration at | ||
| // https://iabeurope.eu/tcf/ and populate this field before EU traffic | ||
| // goes through the adapter, otherwise CMPs may drop bid requests. | ||
| // const GVLID = 0; | ||
|
|
||
| const converter = ortbConverter({ | ||
| context: { | ||
| netRevenue: true, | ||
| ttl: DEFAULT_TTL, | ||
| currency: DEFAULT_CURRENCY, | ||
| }, | ||
|
|
||
| // Per-impression hook: apply the optional per-bid floor override the | ||
| // publisher set via `params.floor`. Only applied when the Price Floors | ||
| // module hasn't already populated `imp.bidfloor` from a floors config. | ||
| imp(buildImp, bidRequest, context) { | ||
| const imp = buildImp(bidRequest, context); | ||
| if (bidRequest.params?.floor != null && !imp.bidfloor) { | ||
| imp.bidfloor = Number(bidRequest.params.floor); | ||
| imp.bidfloorcur = DEFAULT_CURRENCY; | ||
| } | ||
| return imp; | ||
| }, | ||
|
|
||
| // Request-level hook: inject the publisher token into | ||
| // `site.publisher.id`, which is where goadserver's /openrtb2/auction | ||
| // handler resolves the SSP campaign from. | ||
| request(buildRequest, imps, bidderRequest, context) { | ||
| const request = buildRequest(imps, bidderRequest, context); | ||
| if (!request.cur || request.cur.length === 0) { | ||
| request.cur = [DEFAULT_CURRENCY]; | ||
| } | ||
| const token = context.bidRequests?.[0]?.params?.token; | ||
| if (token) { | ||
| deepSetValue(request, 'site.publisher.id', token); | ||
| } | ||
| return request; | ||
| }, | ||
| }); | ||
|
|
||
| /** @type {import('../src/adapters/bidderFactory.js').BidderSpec} */ | ||
| export const spec = { | ||
| code: BIDDER_CODE, | ||
| // gvlid: GVLID, // TODO: populate once registered with IAB Europe | ||
| supportedMediaTypes: [BANNER, VIDEO, NATIVE], | ||
|
|
||
| /** | ||
| * Every bid must carry a host (goadserver deployment domain) and a | ||
| * token (SSP campaign hash from the publisher panel). Without both, | ||
| * the auction can't be authenticated or routed. | ||
| * | ||
| * @param {Object} bid | ||
| * @returns {boolean} | ||
| */ | ||
| isBidRequestValid: function (bid) { | ||
| return Boolean(bid?.params?.host) && | ||
| typeof bid.params.host === 'string' && | ||
| Boolean(bid?.params?.token) && | ||
| typeof bid.params.token === 'string'; | ||
| }, | ||
|
|
||
| /** | ||
| * Build an OpenRTB 2.5 BidRequest and POST it to the publisher's | ||
| * specific goadserver deployment. All bids in a single buildRequests | ||
| * call share the same publisher context (same page, same token) so | ||
| * we emit one BidRequest with N imps to one endpoint URL. | ||
| * | ||
| * @param {Object[]} validBidRequests | ||
| * @param {Object} bidderRequest | ||
| * @returns {ServerRequest} | ||
| */ | ||
| buildRequests: function (validBidRequests, bidderRequest) { | ||
| if (!validBidRequests || validBidRequests.length === 0) { | ||
| return []; | ||
| } | ||
| const host = validBidRequests[0].params.host; | ||
| const url = `https://${host}/openrtb2/auction`; | ||
| const data = converter.toORTB({ | ||
| bidRequests: validBidRequests, | ||
| bidderRequest, | ||
| }); | ||
| return { | ||
| method: 'POST', | ||
| url, | ||
| data, | ||
| options: { contentType: 'application/json', withCredentials: true }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Using Useful? React with 👍 / 👎. |
||
| }; | ||
| }, | ||
|
|
||
| /** | ||
| * Translate goadserver's OpenRTB 2.5 BidResponse back into Prebid | ||
| * bids. ortbConverter handles the bulk of the mapping — we just | ||
| * delegate. | ||
| * | ||
| * @param {Object} serverResponse | ||
| * @param {ServerRequest} request | ||
| * @returns {Bid[]} | ||
| */ | ||
| interpretResponse: function (serverResponse, request) { | ||
| if (!serverResponse?.body) { | ||
| return []; | ||
| } | ||
| return converter.fromORTB({ | ||
| response: serverResponse.body, | ||
| request: request.data, | ||
| }).bids; | ||
| }, | ||
|
|
||
| /** | ||
| * Fire the impression URL when a bid wins. goadserver uses the same | ||
| * `nurl` tracking pattern as its existing RTB path, so triggering the | ||
| * pixel here unifies win notification with the rest of the platform. | ||
| * | ||
| * @param {Bid} bid | ||
| */ | ||
| onBidWon: function (bid) { | ||
| if (bid?.nurl && isStr(bid.nurl)) { | ||
| triggerPixel(bid.nurl); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| registerBidder(spec); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # Overview | ||
|
|
||
| ``` | ||
| Module Name: goadserver Bidder Adapter | ||
| Module Type: Bidder Adapter | ||
| Maintainer: support@goadserver.com | ||
| ``` | ||
|
|
||
| # Description | ||
|
|
||
| Prebid.js adapter for the goadserver platform — a self-hosted, multi-tenant ad serving stack with a built-in OpenRTB 2.5 Prebid Server endpoint. | ||
|
|
||
| One adapter serves every goadserver deployment. The specific ad server to route bids to is passed per-ad-unit via `params.host`, and the publisher's SSP campaign authentication token (generated in the goadserver panel) is passed via `params.token`. Publishers running multiple goadserver instances can mix and match them in a single Prebid.js config by setting different `params.host` values on different ad units. | ||
|
|
||
| Supported media types: `banner`, `video`, `native`. | ||
|
|
||
| # Bid Parameters | ||
|
|
||
| | Name | Scope | Description | Example | Type | | ||
| | -------- | -------- | ------------------------------------------------------------------------------------------------------------- | --------------------- | -------- | | ||
| | `host` | required | The goadserver deployment's public domain. The adapter POSTs to `https://{host}/openrtb2/auction`. | `"ads.example.com"` | `string` | | ||
| | `token` | required | SSP campaign authentication token from the publisher's goadserver panel. Goes into `site.publisher.id`. | `"a1b2c3d4..."` | `string` | | ||
| | `floor` | optional | Per-bid CPM floor (USD). Honored only if the Price Floors module hasn't already set `imp.bidfloor`. | `0.50` | `number` | | ||
|
|
||
| # Test Parameters | ||
|
|
||
| ```js | ||
| const adUnits = [ | ||
| { | ||
| code: "top-banner", | ||
| mediaTypes: { | ||
| banner: { | ||
| sizes: [[728, 90], [970, 250]] | ||
| } | ||
| }, | ||
| bids: [ | ||
| { | ||
| bidder: "goadserver", | ||
| params: { | ||
| host: "ads.example.com", | ||
| token: "your-sspcampaigns-hash", | ||
| floor: 0.50 | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| code: "preroll", | ||
| mediaTypes: { | ||
| video: { | ||
| context: "instream", | ||
| playerSize: [[640, 480]], | ||
| mimes: ["video/mp4"] | ||
| } | ||
| }, | ||
| bids: [ | ||
| { | ||
| bidder: "goadserver", | ||
| params: { | ||
| host: "ads.example.com", | ||
| token: "your-sspcampaigns-hash" | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| code: "native-1", | ||
| mediaTypes: { | ||
| native: { | ||
| title: { required: true }, | ||
| image: { required: true }, | ||
| sponsoredBy: { required: true } | ||
| } | ||
| }, | ||
| bids: [ | ||
| { | ||
| bidder: "goadserver", | ||
| params: { | ||
| host: "ads.example.com", | ||
| token: "your-sspcampaigns-hash" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| ]; | ||
| ``` | ||
|
|
||
| # Multi-Deployment Example | ||
|
|
||
| Two ad units auctioned against two different goadserver deployments in one request: | ||
|
|
||
| ```js | ||
| pbjs.addAdUnits([ | ||
| { | ||
| code: "slot-a", | ||
| mediaTypes: { banner: { sizes: [[300, 250]] } }, | ||
| bids: [{ | ||
| bidder: "goadserver", | ||
| params: { host: "deployment1.example.com", token: "token-a" } | ||
| }] | ||
| }, | ||
| { | ||
| code: "slot-b", | ||
| mediaTypes: { banner: { sizes: [[728, 90]] } }, | ||
| bids: [{ | ||
| bidder: "goadserver", | ||
| params: { host: "deployment2.example.com", token: "token-b" } | ||
| }] | ||
| } | ||
| ]); | ||
| ``` | ||
|
|
||
| # Consent & Privacy | ||
|
|
||
| The adapter honors Prebid.js's standard consent plumbing (`ortbConverter` handles it): GDPR (`regs.ext.gdpr`, `user.ext.consent`), US Privacy (`regs.ext.us_privacy`), GPP (`regs.gpp` + `regs.gpp_sid`), and COPPA (`regs.coppa`). No bidder-specific consent handling is required on the publisher side. | ||
|
|
||
| **GVL ID note:** the goadserver adapter is not yet registered with the IAB Global Vendor List. EU publishers using CMPs that enforce GVL may see bid requests dropped pre-auction until the registration is filed at https://iabeurope.eu/tcf/. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
buildRequestsalways picksvalidBidRequests[0].params.hostand then serializes all bids into one ORTB request, so auctions that include multiple goadserver tenants (differenthostand/ortokenvalues across ad units) will route impressions to the wrong endpoint and apply the wrong publisher token, leading to rejected or misattributed bids. Group requests by tenant key (host+token) and return one server request per group.Useful? React with 👍 / 👎.