Skip to content

Peak226 Bid Adapter: initial release - #15619

Merged
patmmccann merged 7 commits into
prebid:masterfrom
plymedia:peak226-bid-adapter
Sep 30, 2026
Merged

patmmccann merged 7 commits into
prebid:masterfrom
plymedia:peak226-bid-adapter

Conversation

@chanand

@chanand chanand commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Type of change

  • Bugfix

  • Feature

  • New bidder adapter

  • Updated bidder adapter

  • Code style update (formatting, local variables)

  • Refactoring (no functional changes, no api changes)

  • Build related changes

  • CI related changes

  • Does this change affect user-facing APIs or examples documented on http://prebid.org?

  • Other

Description of change

Adds the Peak226 bid adapter (banner, video and native, incl. multiformat ad units).

  • Maintainer: support@edge226.com
  • Built on ortbConverter (OpenRTB 2.6). Floors, eids, schain, first-party data and consent
    (TCF EU / USP / GPP / COPPA) are taken from ortb2 / ortb2Imp; no bidder params duplicate
    ortb2 data.
  • Requests are routed to a regional endpoint (us / eu / jp) and split per
    (region, publisherId) so site|app.publisher.id is always correct.
  • ${AUCTION_PRICE} is resolved in adm, nurl and burl (the SSP returns the macro in VAST
    and notice URLs; core only expands it in banner markup).
  • Bids are net, USD, TTL 300s, meta.advertiserDomains from adomain.
  • No getUserSyncs: GVL 1202 declares usesCookies: false and no Purpose 1, so there is no
    sync to register. No storage is used.
  • TypeScript, with Peak226BidderParams registered in BidderParams.
  • Native responses arrive in a non-standard adm_native field (an already-parsed ORTB native
    object) rather than adm (a JSON string); the adapter maps it over so core's native
    processor can read it, and resolves ${AUCTION_PRICE} recursively inside it since the macro
    turned up nested in a tracker URL that core has no path to expand. Found by hitting the live
    endpoint directly — see "Checks" below.

Contact email of the adapter's maintainer: support@edge226.com

Test parameters for validating bids:

{
  bidder: 'peak226',
  params: { publisherId: 'hb-test', placementId: 'hb-test' }
}

Confirmed live: direct ORTB requests with this pair returned real 200 bid responses for
banner, instream video and native on the US endpoint (a prior placeholder pair, hb123/hb,
204'd — this is a placement the SSP team provisioned to always fill, not an arbitrary string).

Full banner / instream video / native test ad units are in modules/peak226BidAdapter.md.
Instream video needs pbjs.setConfig({ cache: { url: ... } }); outstream needs a publisher
renderer (Peak226 returns VAST and does not host a renderer).

Relationship to the existing edge226 adapter

Both adapters belong to Edge226 Ltd (GVL 1202, see
https://vendor-list.consensu.org/v3/vendor-list.json, same entity confirmed on
prebid/prebid-server#4919). Peak226 is a separate SSP platform, not a rebrand: it runs on its
own stack (*.a.viddea.com, regional data centers), speaks OpenRTB 2.6, and uses different
account parameters (publisherId + placementId + region), whereas edge226 uses the
teqblaze JSON protocol on ssp.dauup.com with a single placementId. An alias cannot change
endpoint, protocol or params, so a dedicated adapter is required. Prebid Server already has a
separate peak226 adapter for the same reason.

GVL name

gvlid: 1202 resolves to "Edge226 Ltd", the legal entity; Peak226 is the product/brand name.
This was raised and verified on the Prebid Server PR (prebid/prebid-server#4919).

Checks

  • npx eslint --no-inline-config modules/peak226BidAdapter.ts test/spec/modules/peak226BidAdapter_spec.js — clean
  • npx tsc and npx tsc -p tsconfig-strict.json — clean
  • npx gulp test --nolint --file test/spec/modules/peak226BidAdapter_spec.js — 37/37 pass (normal build), 30/30 pass (all-features-disabled build)
  • npx gulp update-metadata --no-fetch — peak226 extracted with gvlid: 1202, no naming violations
  • Live endpoint: direct ORTB POSTs to us.a.viddea.com/edge_direct with the documented test
    params returned real bids for banner, video and native

Other information

chanand and others added 4 commits August 5, 2026 15:43
Adds a new bidder adapter for peak226, built on Prebid's ortbConverter
for OpenRTB2-style banner/video/native requests, with region routing
across US/EU/JP endpoints and TCF/USP/GPP/COPPA/floors/schain forwarded
automatically. Endpoint URLs, response-shape and user-sync details are
still placeholders pending confirmation from peak226; see TODOs in
modules/peak226BidAdapter.ts and modules/peak226BidAdapter.md.

gulp lint: clean (no output)
gulp test --file test/spec/modules/peak226BidAdapter_spec.js: 24/24 passing
The adapter already sends every declared format on a single imp and
lets response mtype pick the winner, but nothing asserted it. Add a
multiFormatBid fixture composed from the existing single-format
fixtures, and cover: a multiformat unit validating, a malformed video
block dropping the whole bid (validation is additive, not per-format),
banner + video + native landing on one imp, and banner and video bids
parsed from a single impid.

Also set pbs: true in the .md — the Prebid Server adapter exists and is
registered — and document the multiformat behavior for publishers.
- Use the production regional endpoints (https://{us,eu,jp}.a.viddea.com/edge_direct)
- Split requests per (region, publisherId) so publisher.id is always correct
- Resolve ${AUCTION_PRICE} in adm, nurl and burl (core only expands it in banner markup)
- Pin currency to USD for floors and responses without cur
- Validate bidder params only; core validates media types
- Drop TODO/pending notes; GVL 1202 (Edge226 Ltd) is registered
- Rewrite module docs in repo convention; instream video test unit, outstream renderer note
…test params

Live testing against the production endpoint (hb-test/hb-test) surfaced two issues:

- Native responses carry markup in a non-standard `adm_native` field (an
  already-parsed ORTB native object) instead of `adm` (a JSON string), with
  `adm` left empty. Core's native processor only reads `bid.adm` and throws
  when it's empty, silently dropping every native bid. The adapter now maps
  adm_native onto adm when adm is unset.
- ${AUCTION_PRICE} appears inside native tracker URLs nested under
  adm_native (e.g. eventtrackers[].url), which core never resolves. Added a
  recursive macro resolver for the whole adm_native object rather than a
  fixed list of fields, since which field carries the macro isn't guaranteed.

Also updates the documented test parameters to publisherId/placementId
'hb-test', confirmed via direct requests to return live banner, video and
native bids on the US endpoint.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-15T06:40:38.767142Z d5135a9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

chanand pushed a commit to chanand/prebid.github.io that referenced this pull request Sep 15, 2026
The Prebid.js peak226 adapter (prebid/Prebid.js#15619) is now submitted,
so this page covers both adapters instead of just Prebid Server: flips
pbjs back to true, adds Prebid.js ad unit examples for banner, instream
video and native, and splits the Notes section into per-adapter
Prebid.js / Prebid Server subsections plus a shared Privacy note.
@chanand

chanand commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Related: Prebid Server adapter at prebid/prebid-server#4919, docs at prebid/prebid.github.io#6715.

@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

This PR introduces changes that may not work on all browsers. According to Babel, the following polyfills may be needed, and they are not automatically included:

  • Changes to modules/peak226BidAdapter.ts may need:
    • es.iterator.constructor
    • es.iterator.find
    • es.iterator.map
    • esnext.iterator.constructor
    • esnext.iterator.find
    • esnext.iterator.map
  • Changes to test/spec/modules/peak226BidAdapter_spec.js may need:
    • es.array.with
    • es.iterator.constructor
    • es.iterator.find
    • es.iterator.for-each
    • es.iterator.map
    • es.json.stringify
    • esnext.array.with
    • esnext.iterator.constructor
    • esnext.iterator.find
    • esnext.iterator.for-each
    • esnext.iterator.map
    • esnext.json.parse

The best way to address this is to provide good test coverage, as normal PR checks run unit tests on older browsers.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5135a9feb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread modules/peak226BidAdapter.ts Outdated
Comment on lines +114 to +115
const { publisherId, placementId } = bid.params ?? ({} as Partial<Peak226BidderParams>);
return isNonEmptyId(publisherId) && isNonEmptyId(placementId);

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 Accept identifiers already supplied through ORTB

When a publisher supplies site|app|dooh.publisher.id and imp.tagid through ortb2/ortb2Imp, this validation still rejects the bid unless the same values are duplicated as publisherId and placementId. Both params should be optional overrides of those standard ORTB fields rather than mandatory duplicates.

AGENTS.md reference: AGENTS.md:L67-L67

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5ef1c81 — agreed, both params are now overrides rather than requirements.

publisherId resolves from params.publisherId first and then, per CLIENT_SECTIONS priority (dooh > app > site), from ortb2.{section}.publisher.id. imp.tagid is only overwritten when params.placementId is given, so an ortb2Imp.tagid supplied by the publisher now stands on its own. isBidRequestValid passes when each of the two values comes from either source, and request grouping keys off the resolved publisher id so the one-publisher-per-request invariant still holds when no params are set. Params are now optional in Peak226BidderParams and the docs (here and in prebid/prebid.github.io#6715) describe them as overrides.

Comment thread modules/peak226BidAdapter.ts Outdated
// every bid in a request shares the same publisherId (see buildRequests)
const publisherId = context.bidRequests[0]?.params?.publisherId;
if (isNonEmptyId(publisherId)) {
deepSetValue(request, `${request.app ? 'app' : 'site'}.publisher.id`, String(publisherId));

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 the DOOH client section

For an auction whose global ORTB data contains dooh, the converter's one-client processor retains dooh and removes site, but this wrapper subsequently creates site.publisher.id because only app is checked. The resulting request contains both dooh and site, violating their mutual exclusivity and potentially causing the endpoint to reject DOOH requests; select dooh.publisher.id when request.dooh is present.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 5ef1c81.

The hook now writes to whichever client section actually survived the converter's onlyOneClient processor: CLIENT_SECTIONS.find((s) => hasSection(request, s)) ?? 'site', reusing CLIENT_SECTIONS/hasSection from src/fpd/oneClient.js rather than re-deriving the priority order. A DOOH auction now gets dooh.publisher.id and no site/app keys; covered by a new unit test.

@barecheck

barecheck Bot commented Sep 15, 2026

Copy link
Copy Markdown

Barecheck - Code coverage report

Total: 91.35%

Your code coverage diff: 0.02% ▴

✅ All code changes are covered

Addresses two review findings on prebid#15619.

- isBidRequestValid required publisherId and placementId even when the
  publisher already supplied {dooh|app|site}.publisher.id via ortb2 and
  imp.tagid via ortb2Imp, forcing them to duplicate standard ORTB fields in
  bid params. Both are now resolved from params first and ortb2 second, and a
  bid is valid as long as each value comes from one of the two. imp.tagid is
  only overwritten when placementId is given, so ortb2Imp.tagid stands on its
  own otherwise. Request grouping keys off the resolved publisher id so the
  one-publisher-per-request invariant still holds without params.
- The request hook wrote site.publisher.id whenever ortb2 had no app, so a
  DOOH auction came out with both dooh and site, which are mutually
  exclusive - the converter's onlyOneClient processor had already dropped
  site. It now writes to whichever client section survived that processor,
  reusing CLIENT_SECTIONS/hasSection from src/fpd/oneClient.js.

Docs note that the params are optional overrides of the ortb2 fields.

Tests: 44 pass (normal build), 37 pass (all features disabled). eslint and
both tsc configs clean.
chanand added a commit to chanand/prebid.github.io that referenced this pull request Sep 15, 2026
Following review feedback on prebid/Prebid.js#15619, the Prebid.js adapter no
longer requires publisherId and placementId when the publisher already
supplies {dooh|app|site}.publisher.id and ortb2Imp.tagid as first party data;
the params act as per-ad-unit overrides. Prebid Server still requires both.
Comment thread modules/peak226BidAdapter.md Outdated
| `region` | optional | `string` | Data center to send the request to: `'us'`, `'eu'` or `'jp'`. Default `'us'`. | `'eu'` |

\* Both IDs may instead be supplied as standard ORTB first party data —
`ortb2.{site|app|dooh}.publisher.id` and `ortb2Imp.tagid` — in which case the params are not

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Imp.ext.gpid is typically preferred to imp.tagid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5cd61aa — isBidRequestValid now also accepts ortb2Imp.ext.gpid as a valid placement identifier (alongside params.placementId/ortb2Imp.tagid), since gpid is already forwarded automatically via the ortb2Imp merge and is more broadly supported than tagid. Docs footnote updated to call it out.

Comment thread modules/peak226BidAdapter.md Outdated

| Name | Scope | Type | Description | Example |
|---------------|----------|----------|--------------------------------------------------------------------------|-------------|
| `publisherId` | required* | `string` | Your Peak226 publisher/account ID. | `'pub-123'` |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I would make the entry in the table optional* instead of required*

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5cd61aa — the table now says optional* instead of required* for publisherId/placementId, matching the footnote.

Addresses PR review feedback: imp.ext.gpid is more broadly supported
than imp.tagid for placement identification, so isBidRequestValid now
accepts it as an additional valid signal alongside placementId/tagid.
Also fixes the Bid Params table to say optional* instead of required*
for publisherId/placementId, matching the existing footnote.
@patmmccann
patmmccann merged commit 771fe09 into prebid:master Sep 30, 2026
102 checks passed
@chanand

chanand commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Backport to the 9.53.x-legacy line (needed by AnyClip, which is on Prebid.js 9): #15731

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants