From 9932d25cd807580f6a9ca7ec00d22ed1c13b1db0 Mon Sep 17 00:00:00 2001 From: Pat Diggins Date: Thu, 30 Apr 2026 17:08:59 -0400 Subject: [PATCH] audienceNetwork: Add rewarded video support and allow rewarded interstitials The audienceNetwork adapter currently drops the rewarded inventory signal because PBS ConvertDownTo25 moves imp.rwdd to imp.ext.prebid, and then the adapter clears imp.Ext. This causes Meta to return HTTP 400 for all video impressions since it cannot classify untyped video. Changes: - Extract is_rewarded_inventory from imp.ext.prebid before clearing ext, restore it as imp.Rwdd on the struct for serialization - Set video.ext.videotype = "rewarded" (Meta's preferred signal) when imp.Rwdd == 1 - Narrow the instl+video rejection to allow rewarded video interstitials while still blocking non-rewarded video+instl Test fixtures: - New exemplary/rewarded-video.json (rewarded signal via ext.prebid) - New exemplary/video-non-rewarded.json (is_rewarded_inventory:0 guard) - New exemplary/rewarded-interstitial.json (video+instl+rewarded) - Updated supplemental/invalid-interstitial.json (new error message) Validated against Meta's live endpoint (an.facebook.com/placementbid.ortb): video:{w:0,h:0,ext:{videotype:"rewarded"}},rwdd:1 returns HTTP 200. Meta confirmed (2026-04-30) that videotype:"rewarded" is their preferred signal and imp.rwdd is fully supported. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../exemplary/rewarded-interstitial.json | 146 ++++++++++++++++++ .../exemplary/rewarded-video.json | 144 +++++++++++++++++ .../exemplary/video-non-rewarded.json | 140 +++++++++++++++++ .../supplemental/invalid-interstitial.json | 2 +- adapters/audienceNetwork/facebook.go | 32 +++- 5 files changed, 461 insertions(+), 3 deletions(-) create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-interstitial.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-video.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/video-non-rewarded.json diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-interstitial.json new file mode 100644 index 00000000000..132df2f75f8 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-interstitial.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 940, + "h": 560 + }, + "instl": 1, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + }, + "prebid": { + "is_rewarded_inventory": 1 + } + } + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 0, + "h": 0, + "ext": { + "videotype": "rewarded" + } + }, + "instl": 1, + "rwdd": 1, + "tagid": "123_456" + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [ + { + "bid": [ + { + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":400,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + } + ] + } + ], + "bidid": "654", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "987", + "impid": "test-imp-id", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":400,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "987", + "crid": "987", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-video.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-video.json new file mode 100644 index 00000000000..b708c8d40d4 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/rewarded-video.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + }, + "prebid": { + "is_rewarded_inventory": 1 + } + } + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 0, + "h": 0, + "ext": { + "videotype": "rewarded" + } + }, + "rwdd": 1, + "tagid": "123_456" + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [ + { + "bid": [ + { + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":400,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + } + ] + } + ], + "bidid": "654", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "987", + "impid": "test-imp-id", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":400,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "987", + "crid": "987", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/exemplary/video-non-rewarded.json b/adapters/audienceNetwork/audienceNetworktest/exemplary/video-non-rewarded.json new file mode 100644 index 00000000000..eaf4f614ab7 --- /dev/null +++ b/adapters/audienceNetwork/audienceNetworktest/exemplary/video-non-rewarded.json @@ -0,0 +1,140 @@ +{ + "mockBidRequest": { + "id": "test-req-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 940, + "h": 560 + }, + "ext": { + "bidder": { + "publisherid": "123", + "placementid": "456" + }, + "prebid": { + "is_rewarded_inventory": 0 + } + } + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid" + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "https://an.facebook.com/placementbid.ortb", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Fb-Pool-Routing-Token": [ + "v4_bidder_token" + ] + }, + "body": { + "id": "test-imp-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "minduration": 15, + "maxduration": 30, + "protocols": [2, 3, 5, 6, 7, 8], + "linearity": 1, + "w": 0, + "h": 0 + }, + "tagid": "123_456" + } + ], + "app": { + "id": "app-abc", + "bundle": "com.prebid", + "publisher": { + "id": "123" + } + }, + "device": { + "ip": "152.193.6.74" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "v4_bidder_token" + }, + "tmax": 500, + "ext": { + "authentication_id": "4e24a2b23fbfb5e41a9093b921d6cddf497c24dd5f63879038cec2ab2f27d174", + "platformid": "test-platform-id" + } + }, + "impIDs": ["test-imp-id"] + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-imp-id", + "seatbid": [ + { + "bid": [ + { + "id": "987", + "impid": "test-imp-id", + "price": 1.000000, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + } + ] + } + ], + "bidid": "654", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "987", + "impid": "test-imp-id", + "price": 1, + "adm": "{\"type\":\"ID\",\"bid_id\":\"987\",\"placement_id\":\"123_456\",\"resolved_placement_id\":\"123_456\",\"sdk_version\":\"5.5.0\",\"device_id\":\"abc\",\"template\":1,\"payload\":null,\"bid_time_token\":\"v4_bidder_token=\"}", + "adid": "987", + "crid": "987", + "nurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "lurl": "https://www.facebook.com/audiencenetwork/nurl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&app_version=iOS-1.0", + "burl": "https://www.facebook.com/audiencenetwork/burl/?partner=test-platform-id&app=def&placement=456&auction=123&impression=123&request=123478&bid=987&clearing_price=${AUCTION_PRICE}" + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json index 68ca8044812..b98ff8cbf85 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/invalid-interstitial.json @@ -34,7 +34,7 @@ "tmax": 500 }, "expectedMakeRequestsErrors": [{ - "value": "imp #test-imp-id: interstitial imps are only supported for banner", + "value": "imp #test-imp-id: interstitial imps are only supported for banner and rewarded video", "comparison": "literal" }] } \ No newline at end of file diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index acf618ecb27..c1a28254fe8 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -150,9 +150,24 @@ func (a *adapter) modifyRequest(out *openrtb2.BidRequest) error { return err } + // Preserve rewarded signal before clearing ext. For ORTB 2.5 adapters, + // PBS moves imp.rwdd to imp.ext.prebid.is_rewarded_inventory via ConvertDownTo25. + // Since we clear imp.Ext below, the signal would be lost. Extract it now and + // restore it on the struct field so it serializes as imp.rwdd for Meta. + isRewarded := imp.Rwdd == 1 + if !isRewarded && imp.Ext != nil { + if val, err := jsonparser.GetInt(imp.Ext, "prebid", "is_rewarded_inventory"); err == nil && val == 1 { + isRewarded = true + } + } + imp.TagID = pubId + "_" + plmtId imp.Ext = nil + if isRewarded { + imp.Rwdd = 1 + } + if out.App != nil { app := *out.App app.Publisher = &openrtb2.Publisher{ID: pubId} @@ -170,8 +185,10 @@ func modifyImp(out *openrtb2.Imp) error { impType := resolveImpType(out) if out.Instl == 1 && impType != openrtb_ext.BidTypeBanner { - return &errortypes.BadInput{ - Message: fmt.Sprintf("imp #%s: interstitial imps are only supported for banner", out.ID), + if impType != openrtb_ext.BidTypeVideo || out.Rwdd != 1 { + return &errortypes.BadInput{ + Message: fmt.Sprintf("imp #%s: interstitial imps are only supported for banner and rewarded video", out.ID), + } } } @@ -299,6 +316,17 @@ func modifyImpCustom(jsonData []byte, imp *openrtb2.Imp) ([]byte, error) { videoMap["w"] = json.RawMessage("0") videoMap["h"] = json.RawMessage("0") + // For rewarded video impressions, set Meta's preferred signal. + // Meta requires video.ext.videotype to classify the video format. + if imp.Rwdd == 1 { + extMap, ok := videoMap["ext"].(map[string]interface{}) + if !ok { + extMap = make(map[string]interface{}) + } + extMap["videotype"] = "rewarded" + videoMap["ext"] = extMap + } + case openrtb_ext.BidTypeNative: nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") if !ok {