Repository navigation
New Adapter: Panxo - #4723
New Adapter: Panxo#4723css12s wants to merge 5 commits into
Conversation
Panxo detects and monetizes AI-referred traffic (ChatGPT, Perplexity, Claude, Gemini) with premium native ad formats. This adapter enables server-side bidding through Prebid Server. - Banner media type support (site only, no app) - Property key based endpoint routing - GVL ID 1527 for TCF 2.2 compliance - GPP support - Redirect-based user sync - Full JSON test coverage (exemplary + supplemental) Owner: Panxo Stacks, Inc.
Code coverage summaryNote:
panxoRefer here for heat map coverage report |
Adapter files, test fixtures, and installation instructions for adding Panxo server-side bidding to an existing Prebid Server build. Pending official Prebid Server review: prebid/prebid-server#4723
|
@bsardo Can you merge when u can? Thanks |
Code coverage summaryNote:
panxoRefer here for heat map coverage report |
|
Open and link a docs PR. A
|
| } | ||
| } | ||
|
|
||
| reqJSON, err := json.Marshal(request) |
There was a problem hiding this comment.
Use jsonutil.Marshal instead of encoding/json (panxo.go:75). The encoding/json import is only used for this one call, so switch to jsonutil.Marshal(request) and drop the import.
"Adapters must use
jsonutil.Marshalandjsonutil.Unmarshalinstead of the Go standard package functions, as those are not optimized for Prebid Server's high performance needs." —
[Prebid Server · Adding a New Bidder (Go)](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html)
| if response.StatusCode == http.StatusNoContent { | ||
| return nil, nil | ||
| } | ||
|
|
||
| if response.StatusCode == http.StatusBadRequest { | ||
| return nil, []error{&errortypes.BadInput{ | ||
| Message: fmt.Sprintf("unexpected status code: %d", response.StatusCode), | ||
| }} | ||
| } | ||
|
|
||
| if response.StatusCode != http.StatusOK { | ||
| return nil, []error{&errortypes.BadServerResponse{ | ||
| Message: fmt.Sprintf("unexpected status code: %d", response.StatusCode), | ||
| }} | ||
| } |
There was a problem hiding this comment.
Reuse the response-status helpers (panxo.go:96-110). Your manual 204/400/non-200 handling is correct, but adapters.IsResponseStatusCodeNoContent(response) and adapters.CheckResponseStatusCodeForErrors(response) do exactly this — worth using them instead of hand-rolling.
| }} | ||
| } | ||
|
|
||
| if bidderExt.PropertyKey == "" { |
There was a problem hiding this comment.
propertyKey goes into the endpoint URL via fmt.Sprintf without escaping (panxo.go:80); a value with &/space would break the query. Wrap it in url.QueryEscape (or build with url.Values), or add a pattern/maxLength to the schema.
- The
propertyKey == ""check (:52) is already guaranteed by the schema (required+minLength:1), so it's effectively dead — safe to drop (keep the cross-imp consistency loop, that one's useful). - The consistency loop
continues on a later imp'simp.extparse error (:62-67), silently skipping the check for that imp while still sending it — consider erroring for consistency with imp[0]. - Media type is hardcoded to banner (
:123) — fine for a banner-only adapter, just noting for the future. - Consider supplementals for a 400/non-200 response, a malformed JSON body, and the multi-imp
propertyKey-mismatch path. endpointCompressionisn't set — does the endpoint support gzip? And the syncuserMacrois empty with no UID macro in the URL — is that intended?
|
$ curl -i --location --request POST HTTP/2 204 → HTTP 204 No Content (~278ms, empty body) — endpoint reachable; no real bid returned, as expected for a test request. |
- Use jsonutil.Marshal instead of encoding/json - Use adapters.IsResponseStatusCodeNoContent and adapters.CheckResponseStatusCodeForErrors in MakeBids - URL-escape propertyKey when building the endpoint query - Error instead of silently skipping imps whose ext fails to parse - Drop the propertyKey empty check already guaranteed by the schema - Set the usersync userMacro so the redirect carries the Panxo uid - Send the TC string as gdpr_consent in the sync URL, matching what the sync endpoint reads - Add supplementals: 400, 500, malformed body, multi-imp key mismatch, invalid imp.ext (first and later imp), no impressions, propertyKey escaping
|
Thanks for the thorough review @postindustria-code. Everything is addressed in the commit above. Docs PR: prebid/prebid.github.io#6494 It was opened on 20 March, the same day as this PR, but never linked here, which is why you could not find it. @muuki88 has already reviewed it and confirmed it merges once this one does. Changes
New supplementals
Adapter coverage goes from 65.9% to 97.5% ( Your two questions
While double-checking the sync flow against our endpoint we also found the sync URL was passing the TC string as Banner-only is intentional, it is the only format we serve today. On the maintainer address: |
|
@css12s, I sent an email for verification from the following address: marina.lebedeva@postindustria.com |
|
@css12s, do not forget upgrade your branch: |
The answer has been received.
|
|
@postindustria-code Seams merging is still blocked "At least 2 approving reviews are required by reviewers with write access." |
|
@przemkaczmarek, @linux019 |
| @@ -0,0 +1,15 @@ | |||
| endpoint: "https://panxo-sys.com/openrtb/2.5/bid" | |||
| endpoint: "https://panxo-sys.com/openrtb/2.5/bid" | ||
| maintainer: | ||
| email: "prebid@panxo.ai" | ||
| gvlVendorID: 1527 |
Code coverage summaryNote:
panxoRefer here for heat map coverage report |
| } | ||
| } | ||
|
|
||
| reqJSON, err := jsonutil.Marshal(request) |
There was a problem hiding this comment.
imp.Ext is forwarded to Panxo's endpoint as-is, which means
imp.ext.bidder — a Prebid-internal wrapper — is sent to the SSP.
Panxo's server should not receive the bidder key.
Before marshaling, each imp's Ext should be cleaned up or replaced.
The minimal fix is to set imp.Ext = nil (or replace it with just
the raw bidder params) before sending:
requestCopy := *request
impsCopy := make([]openrtb2.Imp, len(request.Imp))
copy(impsCopy, request.Imp)
for i := range impsCopy {
impsCopy[i].Ext = nil
}
requestCopy.Imp = impsCopy
reqJSON, err := jsonutil.Marshal(&requestCopy)
The fixture simple-banner.json confirms the current (wrong) behavior:
expectedRequest.body.imp[0].ext.bidder is present in the outgoing
request, which means this is not just a fixture issue.
| for i := range sb.Bid { | ||
| bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ | ||
| Bid: &sb.Bid[i], | ||
| BidType: openrtb_ext.BidTypeBanner, |
There was a problem hiding this comment.
BidType is hardcoded to BidTypeBanner regardless of what mtype
the SSP returns on the bid. If Panxo ever returns a bid with a
different mtype (e.g. native), it will be silently mistyped as banner.
PBS convention is to determine bid type from bid.MType, with a
fallback to impression media type. At minimum, please return an error
when the mtype is unexpected rather than silently coercing it:
bidType, err := getMediaTypeForBid(bid)
if err != nil {
errs = append(errs, err)
continue
}
Since the bidder-info YAML only declares banner, this may be
intentional — but it should be documented with a comment explaining
why mtype is not checked.
| endpoint string | ||
| } | ||
|
|
||
| func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { |
There was a problem hiding this comment.
Unused parameters should be named _ per PBS convention:
func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error)
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error)
func (a *adapter) MakeBids(_ *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error)
| } | ||
|
|
||
| func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
| var errs []error |
There was a problem hiding this comment.
errs is declared at the top of MakeRequests but is always nil at
the point it is returned (return [...], errs). The only other use
is return nil, append(errs, err) in the marshal error branch, where
it is equivalent to return nil, []error{err}.
Remove the var errs []error declaration and use nil / []error{err}
directly to avoid misleading readers into thinking errors accumulate here.
| @@ -0,0 +1,15 @@ | |||
| endpoint: "https://panxo-sys.com/openrtb/2.5/bid" | |||
There was a problem hiding this comment.
The endpoint URL path is /openrtb/2.5/bid but openrtb.version: 2.6
is declared. These are inconsistent — the path suggests a 2.5 API
while the declaration tells PBS to pass 2.6-specific fields through.
If Panxo's endpoint accepts OpenRTB 2.6 requests (the path is just a
label), please confirm. If it is truly a 2.5 endpoint, remove the
version: 2.6 line to avoid unexpected field forwarding.
| @@ -0,0 +1,124 @@ | |||
| { | |||
There was a problem hiding this comment.
The supplemental fixtures cover error paths thoroughly. However, the
primary validation logic — that all imps must share the same propertyKey
— has no happy-path test exercising it. Please add:
exemplary/multi-imp.json — two imps with the same propertyKey,
verifying that both are forwarded in a single request and both bids
are returned.
Without this fixture, the validation loop in MakeRequests (lines 52-71)
is only exercised by the error case (mismatched-property-keys.json).
| openrtb: | ||
| version: 2.6 | ||
| gpp-supported: true | ||
| capabilities: |
There was a problem hiding this comment.
please check if you support gzip and add
endpointCompression: gzip
Code coverage summaryNote:
panxoRefer here for heat map coverage report |



Adds the Panxo OpenRTB 2.6 adapter for banner, video and native inventory.
This update addresses review feedback:
Adapter tests and the exchange QA check pass locally. Endpoint gzip support is not declared. Private-deal fulfillment is not supported; app/CTV fixtures are protocol tests only.
PBS uses standard OpenRTB floors. The JS adapter's deprecated floor parameter remains a compatibility point for maintainer review.
Docs: prebid/prebid.github.io#6494
Maintainer: prebid@panxo.ai
JavaScript update: prebid/Prebid.js#15875