Skip to content

New Adapter: Epom Ad Server - #4916

Open
ruin wants to merge 5 commits into
prebid:masterfrom
epom:epom_as-bid-adapter
Open

New Adapter: Epom Ad Server#4916
ruin wants to merge 5 commits into
prebid:masterfrom
epom:epom_as-bid-adapter

Conversation

@ruin

@ruin ruin commented Aug 20, 2026

Copy link
Copy Markdown

New server-side adapter for Epom Ad Server (epom_as), banner only, GVL vendor id 849.
Maintainer: support@epom.com

Epom Ad Server is white-label: each network runs its own deployment on its own domain, so the
endpoint is templated on a per-impression host parameter and the adapter posts to
https://{{.Host}}/hb/bid. The host pattern in the params schema is the same expression
util/urlutil.IsSafeHost applies, so a bid the Prebid.js adapter accepts is one this adapter
accepts too. placementKey is sent as imp.tagid; channel and customParams ride in
imp.ext.epom_as and imp.ext.data, where a key already on the impression wins. bidFloor is
applied only when the request carries no floor of its own, so a Price Floors result always wins.

Tests: 6 exemplary and 17 supplemental fixtures, 89.3% statement coverage. The supplemental set
covers the failure paths — 400 and 500 responses, an unparseable body, an unresolvable media type, a
malformed imp.ext, a partial failure across several impressions, an empty seatbid, and a bid whose
impid matches no impression. params_test.go exercises both sides of every declared constraint.

Why a second Epom bidder rather than a change to epom

epom is already registered here, currently disabled. It is the other side of the trade: a
demand-side connector pointing at an.epom.com/ortb, where Epom acts as the buyer. epom_as is the
sell-side of an Epom Ad Server deployment, where Epom is the seller: a different endpoint, on the
publisher network's own domain, with a different parameter contract keyed per impression on a
placement identifier. Neither is an alias or a fix of the other, and a publisher integrating one has
no use for the other. Renaming or repurposing epom would break the existing configuration of
anyone who re-enables it.

Related

epomrnd and others added 4 commits August 20, 2026 11:52
Server-side counterpart of the epom_as Prebid.js adapter. Same bidder
code, same params and the same wire format, so a publisher can move
between client-side and S2S without reconfiguring.

Distinct from the existing epom adapter, which is the Epom DSP: the DSP
buys impressions, this one sells a publisher's own inventory.

The platform is white-label, so the host arrives per impression in
imp.ext.bidder.host and resolves into the endpoint template. Impressions
sharing a host stay in one request because the ad server resolves a page
as a unit. The host is validated with urlutil.IsSafeHost so a publisher
config cannot rewrite the outbound URL.

Banner only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirrors the Prebid.js adapter so both transports put the same data in the
same place: channel under imp.ext.epom_as, custom parameters merged into
imp.ext.data alongside first-party data, and the same ingest limits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The schema declared four parameters where the Prebid.js adapter accepts six, so a
publisher moving a working client-side configuration to Prebid Server would have
bidFloor and bidFloorCur silently rejected — and an earlier commit claimed the two
were in step.
bidFloor and bidFloorCur were declared in static/bidder-params and documented, but ExtImpEpomAs
carried neither and the adapter read neither, so a publisher's floor was validated and then thrown
away — while the same configuration worked on Prebid.js. They are now on the ext struct and applied
with the same precedence the JS adapter uses: only when the request carries no floor of its own, so
a Price Floors result always wins. Currency defaults to USD when a floor comes from the params.

The custom-parameter caps are gone from both the schema prose and the merge. They were enforced by
dropping entries mid-range over a Go map, whose iteration order is randomised, so which 32 keys
survived changed from request to request and the emitted imp.ext was not reproducible. Type
validation stays in the schema, which is where every other adapter with an object parameter leaves
it. The ad server applies its own ingest limits and ignores the rest.

channel loses its 128-character cap: it existed on this side only, and a value Prebid.js accepts
should not vanish server-side. bidFloorCur loses its ^[A-Z]{3}$ pattern for the same reason — it was
the only occurrence of that clause across every bidder-params file.

Tests go from three supplemental fixtures to seventeen plus two exemplary, covering the paths that
previously had none: 400 and 500 responses, an unparseable body, an unresolvable media type, a
malformed imp.ext, a partial failure across several impressions, an empty seatbid, and a bid whose
impid matches nothing. params_test now exercises both sides of every declared constraint.
The ad server decides geo targeting, IP targeting, IP blacklists and its
consent country fallback from the address it sees on the call. On a
server-to-server request that address is this host, never the reader, so
every one of those decisions was being made about the wrong machine — a
visitor in Warsaw priced and targeted as whatever country the Prebid
Server instance sits in. The request already carries the right address on
device.ip; it simply never reached a header.

Forwards it as X-Forwarded-For, IPv6 first, matching the 48 adapters in
this repository that already do the same and the 31 of those that carry
both families.

The exemplary case that covers it also states the headers, which is what
makes the harness compare them at all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants