內部中介服務未公開供用戶使用
使用 Audience Network 的內部出價目前為封閉測試版,未公開供用戶使用。若有任何變更,我們將提供進一步的更新。
您可以改為透過我們合作的其中一個中介服務平台存取 Audience Network 出價。
本指南說明組建內部競價伺服器的方法。在下方的逐步指南中,我們會使用在插頁廣告出價的範例。請確認您已熟悉 Audience Network 插頁廣告的使用方法。出價同時支援原生、橫幅、插頁、插播影片和獎勵式影片格式。您可以在下方參閱支援的廣告格式),整合不同的廣告格式。
我們提供的程式碼範本只用於示範出價 API 運作方式,以及您將這個 API整合於內部競價伺服器的方法。競價伺服器範例以 Python 加 Flask 寫成,為保持原始碼的簡約程度,只會以最低限度的完整性與安全性為考量。
ORTB 欄位 imp.tagid
中的資料是庫存的 Audience Network 識別碼。在 Audience Network 中是以編號表示庫存。
傳送至 Audience Network 出價端點的 ORTB 要求,必須包含裝置資訊和應用程式資訊。以下是 ORTB 要求中的承載範例:
{ // Device information from client side 'device': { 'ifa': '${DEVICE_ID}', 'dnt': '0', 'ip': '127.0.0.1' }, // Application information 'app': { 'ver': '1.0', 'bundle': 'com.facebook.audiencenetwork.AdUnitsSample', 'publisher': { // For server to server bidding integration, this is your application id on Facebook 'id': '${APP_ID}', } }, // Placement information we can store on server 'imp': [ { 'id': 'banner_test_bid_req_id', // This is the placement id for Audience Network 'tagid': '${PLACEMENT_ID}', 'banner': { 'w': -1, 'h': 50, }, }, ], // Optional regulations object from client side 'regs': { 'coppa': 0, }, // In server to server integration, you can use the Facebook app id as platform id here 'ext': { 'platformid': '${PLATFORM_ID}', // Mediation partner Platform ID or publisher FB app ID, mandatory. 'authentication_id': '${AUTHENTICATION_ID}', // Authentication token to validate the originator of the request. 'security_app_id': '${AN_SECURITY_APP_MY_SECURITY_APP_ID}', // Security app id used to generate authentication_id. 's2s_version': '${VERSION_NUMBER}', // Version of the integration. Max 16 characters. }, // buyeruid is the user bidder token generated on client side, using the `getBidderToken` method from the Audience Network SDK. // It's constant through out app session so you could cache it on the client side 'user': { 'buyeruid': 'mybuyeruid', }, // Test mode flag 'test': '1', // Time out setting we can store on server 'tmax': 1000, // Request ID we can generate on server 'id': 'banner_test_bid_req_id', // Auction setting we can store on server 'at': 1, }
參數 | 類型 | 使用方式 |
---|---|---|
|
| 如果您是中介服務合作夥伴,這就是您的 Facebook 聯絡人提供的合作夥伴編號。 |
如果您是要整合內部解決方案的發佈商,這就是您傳送出價要求所為的應用程式編號。這是版位編號中出現在底線之前的第一個部分。 | ||
|
| 用來驗證要求始發者的驗證權杖。請參閱此頁面。 |
|
| 用來產生 authentication_id 的安全應用程式編號。請參閱此頁面。 |
|
| 整合的版本。上限 16 個字元,由發佈商產生。 |
以上 ORTB 要求承載是 Audience Network 的必要項目。不過,在您自己的競價伺服器上,您其實可以定義一些應用程式規格,例如應用程式編號、版位編號,以及伺服器設定中的一些其他參數,那麼您的用戶端應用程式就只需要傳送簡易版的要求。您的伺服器可以查詢版位資訊並建構最終版的 ORTB 要求。以下是競價伺服器設定範例:
{ "bidding_source_platforms": [ { "platform_name": "audience_network", "end_point": "https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb", "timeout": 1000, "timeout_notification_url": "https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&auction=${AUCTION_ID}&ortb_loss_code=2" } ], "apps": [ { "app_id": "101", "app_name": "My example app", "placements": [ { "placement_id": "1", "placement_name": "My example placement", "ad_format": "interstitial", "bidding_source_placement_ids":[ { "platform_name": "audience_network", "platform_app_id": "${APP_ID}", "platform_placement_id": "${PLACEMENT_ID}" } ] } ] } ] }
參數 | 使用方式 |
---|---|
| 如果您是中介服務合作夥伴,這就是您的 Facebook 聯絡人提供的合作夥伴編號。 |
如果您是要整合內部解決方案的發佈商,這就是您傳送出價要求所為的應用程式編號。這是版位編號中出現在底線之前的第一個部分。 |
若使用以上競價伺服器設定,用戶端應用程式只需要將包含 "app_id":"101"
和 "placement_id":"1"
的簡易版要求傳送到您的伺服器,您的伺服器就可以查詢完整的版位資訊,例如 placement_id
、ad_format
、platform_name
等等。這是您的用戶端發出的簡易版要求承載範例:
{ // App ID and placement ID are used to look up settings from // server settings 'app_id': '101', 'placement_id': '1', 'bundle': 'com.facebook.samples.s2sbiddingclient', 'bundle_version': '1.0', // Device specifics 'ifa': '${DEVICE_ID}', 'coppa': 0, 'dnt': 0, // buyer_tokens are the user tokens required for different networks 'buyer_tokens': { // Token for audience network from BidderTokenProvider.getBidderToken(context) // This can be cached for the same app session 'audience_network': 'my-buyeruid', }, }
將簡易版要求傳送到您的伺服器之後,您應可以查詢完整的版位、裝置和應用程式資訊,方法是使用競價伺服器設定中的 app_id
和 placement_id
。這是查詢結果範例:
{ "placement_id": "1", "placement_name": "My example placement", "ad_format": "interstitial", "bidding_source_placement_ids": [{ "platform_name": "audience_network", "platform_app_id": "${APP_ID}", "platform_placement_id": "${PLACEMENT_ID}" }] }
競價伺服器收到競價要求之後,需為每一個需求來源建構出價要求。這是 app.py
中用於接收競價要求的端點:
@app.route('/get_bid', methods=['POST']) def get_bid(): ''' The actual endpoint that expects a ClientRequest in the parameters ''' # Initialize the server settings ServerSettings.initialize( app.config['bidding_source_platforms'], app.config['apps'] ) request_params = request.get_json(force=True) # See the code sample below for this function in `bid_manager.py` (code, response) = get_bid_response( request.remote_addr, request.user_agent.string, request_params) app.logger.debug('server response: {0} {1}'.format(code, response)) return Response( json.dumps(response), status=code, mimetype='application/json' )
在 bid_manager.py
中,用戶端傳來的競價要求會包含 app_id
和 placement_id
,其作用是能在伺服器設定中查詢完整版位。
# Return Auction Result - Winner - back to client side def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" try: app_id = request_params['app_id'] placement_id = request_params['placement_id'] auction_id = get_auction_id() ... # Find placement in the settings placement = ServerSettings.get_placement(app_id, placement_id) # Collect bid requests for different platforms bid_requests = get_bid_requests( ip, user_agent, auction_id, placement, request_params) except Exception as e: raise ParameterError("Error in request parameters: {}".format(str(e))) ... return final_response # Get all bid requests for different platforms def get_bid_requests(ip, user_agent, auction_id, placement, request_params): """Create bid requests based on the internal placement setting""" ... (end_point, data, timeout_notification_url) = get_bid_request( ip, user_agent, auction_id, platform_name, platform_app_id, platform_placement_id, ad_format, request_params) if data is not None: results.append({ 'platform_name': platform_name, 'end_point': end_point, 'data': data, 'timeout_notification_url': timeout_notification_url, }) # current_app.logger.debug("requests: {}".format(results)) return results # Get bid request for each platform def get_bid_request( ip, user_agent, auction_id, platform_name, platform_app_id, platform_placement_id, ad_format, request_params ): """Create bid request for a specific platform""" if platform_name == 'audience_network': return audience_network.get_bid_request( ip, user_agent, auction_id, platform_app_id, platform_placement_id, ad_format, request_params) else: return (None, None, None)
接著在 audience_network.py
中,get_bid_request
函式會依據完整的版位資訊,產生 Audience Network 所需的最終版 ORTB 要求。
ORTB 欄位 imp.tagid
包含庫存的 Audience Network 識別碼。在 Audience Network 中是以版位編號表示庫存。
def get_bid_request( ip, user_agent, auction_id, platform_app_id, platform_placement_id, ad_format, request_params ): ''' Gather the required bid request parameters for networks. The parameters consist of platform settings like app id, placement ids, ad sizes etc., and client side information such as device information, user agent etc. We use the `settings.json` file to store platform specific settings, and the client request to retrieve the clietn specific information. ''' platform = ServerSettings.get_platform('audience_network') end_point = platform['end_point'] timeout = platform['timeout'] timeout_notification_url = platform['timeout_notification_url'] timeout_notification_url.replace('${PARTNER_FBID}', platform_app_id) timeout_notification_url.replace('${APP_FBID}', platform_app_id) timeout_notification_url.replace('${AUCTION_ID}', auction_id) imp = [] if ad_format == 'native': imp.append({ 'id': auction_id, 'native': { 'w': -1, 'h': -1, }, 'tagid': platform_placement_id, }) elif ad_format == 'banner': imp.append({ 'id': auction_id, 'banner': { 'w': -1, 'h': 50, }, 'tagid': platform_placement_id, }) elif ad_format == 'interstitial': imp.append({ 'id': auction_id, 'banner': { 'w': 0, 'h': 0, }, 'tagid': platform_placement_id, 'instl': 1, }) elif ad_format == 'rewarded_video': imp.append({ 'id': auction_id, 'video': { 'w': 0, 'h': 0, 'linearity': 2, }, 'tagid': platform_placement_id, }) elif ad_format == 'instream_video': imp.append({ 'id': auction_id, 'video': { 'w': 0, 'h': 0, 'linearity': 1, }, 'tagid': platform_placement_id, }) else: raise ParameterError("Incorrect ad format") typed_ip = ipaddress.ip_address(ip) device = { 'ifa': request_params['ifa'], 'ua': user_agent, 'dnt': request_params['dnt'], } if type(typed_ip) is ipaddress.IPv6Address: device['ipv6'] = ip else: device['ip'] = ip # Construct the ORTB request request = { 'id': auction_id, 'imp': imp, 'app': { 'bundle': request_params['bundle'], 'ver': request_params['bundle_version'], 'publisher': { 'id': platform_app_id, } }, 'device': device, 'regs': { 'coppa': request_params['coppa'], }, 'user': { 'buyeruid': request_params['buyer_tokens']['audience_network'], }, 'ext': { ' ': platform_app_id, }, 'at': 1, 'tmax': timeout, 'test': request_params['test'], } return (end_point, request, timeout_notification_url)
我們目前支援可透過 OpenRTB 要求的 4 種廣告類型:橫額廣告、原生廣告(原生廣告或原生橫額廣告)、影片廣告(獎勵式影片或插播影片)及插頁廣告。備註:橫額廣告、原生廣告和影片物件之間互不相容,但必須有其中一個。
以下是出價要求所支援的廣告格式清單:
廣告格式 | 出價要求中的參數 |
---|---|
原生廣告 |
|
原生橫額廣告 |
|
插頁廣告 |
|
獎勵式影片 |
|
獎勵式插頁廣告 |
|
橫額廣告(高度:50) |
|
橫額廣告(高度:250*) |
|
*您可以在營利管理工具中為此廣告格式建立橫額廣告版位或中型長方形廣告版位
將按照上述步驟建立 ORTB 要求物件後,我們就可以將使用 post
和 Content-Type: application/json
,以 HTTP 將要求傳送至網址在 https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb
的 Audience Network 端點。
在 bid_manager.py
中,待蒐集到每一個平台的所有出價要求之後,就會呼叫各個平台的 exec_bid_requests:
# Return Auction Result - Winner - back to client side def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" ... # Execute bid requests by network bid_responses = [] for bid_request in bid_requests: (code, response) = exec_bid_request( bid_request['platform_name'], bid_request['end_point'], bid_request['data'], bid_request['timeout_notification_url']) bid_responses.append({ 'platform_name': bid_request['platform_name'], 'code': code, 'response': response, }) final_response = run_auction(bid_responses, placement) return final_response # Execute bid request for different platform (network) def exec_bid_request( platform_name, end_point, request_params, timeout_notification_url ): ''' Actually run the bid requests for the networks. ''' if platform_name == 'audience_network': return audience_network.exec_bid_request( end_point, request_params, timeout_notification_url, ) else: raise InternalError("Invalid platform: {}".format(platform_name))
要求中會設定下列 HTTP 標頭(針對出價或未出價),其中包含有助於疑難排解且應記錄在競價伺服器上的資訊:
X-FB-AN-Request-ID
:Audience Network 需使用要求編號才能針對特定要求進行偵錯。申請支援時,請務必出示這個編號。X-FB-AN-Errors
:列有所有已知錯誤的清單,有助於瞭解未出價的原因。X-FB-Debug
:與這項要求相關的一些資訊,您可以將這些資訊傳送給您的 Audience Network 客戶代表,由對方進行疑難排解。備註:為盡可能避免誤時,您必須在出價要求中加註 X-FB-Pool-Routing-Token
標頭。
X-FB-Pool-Routing-Token
:這個權杖用於將要求轉傳給我們最近的資料中心,權杖值則與 user.buyeruid
的值相同。 在 audience_network.py
中,會將 ORTB 要求傳送至 Audience Network,並將對應的出價回應傳送至另一個競價伺服器:
def exec_bid_request( end_point, request_params, timeout_notification_url ): try: platform = ServerSettings.get_platform('audience_network') headers = { 'Content-Type': 'application/json; charset=utf-8', 'X-FB-Pool-Routing-Token': request_params['user']['buyeruid'], } timeout = platform['timeout'] r = requests.post( end_point, json=request_params, headers=headers, # Timeout in settings.json in ms timeout=(timeout / 1000), ) except Exception as e: current_app.logger.error(BID_TIMEOUT) # Send time out notification r = requests.get(timeout_notification_url, timeout) return (500, BID_TIMEOUT) if r.status_code == requests.codes.ok: try: data = json.loads(r.text) current_app.logger.debug('Audience Network response: {}'.format( data )) # Parse response from Audience Network with the ORTBResponse ortb_response = ORTBResponse(data) except Exception as e: current_app.logger.error( PARSE_ERROR + "{}".format(e) ) return (500, PARSE_ERROR + "{}".format(e)) return (r.status_code, ortb_response) else: # The error message is stored in the X-FB-AN-Errors header error_header = r.headers.get('x-fb-an-errors') debug_header = r.headers.get('x-fb-debug') bid_request_id = r.headers.get('x-fb-an-request-id') if r.status_code == 400: error_message = INVALID_BID + error_header + INVALID_BID_ADVICE elif r.status_code == 204: error_message = NO_BID + error_header else: error_message = UNEXPECTED_ERROR # Log error information for debugging error = { 'bid_request_id': bid_request_id, 'debug_header': debug_header, 'error_message': error_message, } current_app.logger.error(error) # Respond error status code to client return (r.status_code, error_message)
如果向要求提供 Content-Encoding:gzip
標頭,則可以傳遞二進位 gzip 壓縮格式的要求內文。
現在我們已經收到不同平台(網路)發出的出價回應了。在 bid_manager.py
中,get_bid_response
函式會負責比對平台回應的出價,並且決定最高出價,也就是得標者。
def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" ... final_response = run_auction(bid_responses, placement) return final_response def run_auction(bid_responses, placement): """Run auction based on raw responses and create the response object""" other_bid = 1 response = (204, None) # default is 204 no fill for bid_response in bid_responses: if bid_response['platform_name'] == 'audience_network': if bid_response['code'] == 200: ortb_response = bid_response['response'] if ortb_response.price > other_bid: response = create_response(bid_response, placement) current_app.logger.debug( 'Audience Network bid: {} won!'.format( ortb_response.price ) ) notify_result(bid_response) else: current_app.logger.debug( 'Audience Network bid: {} lost!'.format( ortb_response.price ) ) notify_result(bid_response, 102) else: current_app.logger.debug(bid_response['response']) return response
在我們的範例中,Audience Network 是唯一的出價工具,因此,執行競價方法只單純比較傳回的出價與某項價格值,然後決定 Audience Network 是否在競價中順利得標。因此,如果 Audience Network 傳回的出價高於 1 美元,我們的回覆會是 Audience Network 得標,否則就視同本次競價未得標。
We require win, loss, timeout and display notifications with the appropriate loss codes as defined in ORTB. ORTB nurl and lurl are provided in the bid response. Please check previous section for bid response example. In case of bid timeout, we provide you with an alternative reporting route.
The win nurl will be provided in the bid response. You need populate Clearing Price in nurl:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&phase=${PHASE}"
${AUCTION_PRICE}
: This should be replaced with the Clearing Price for the auction in the same unit as our bid (i.e. USD on CPM basis).${PHASE}
: This should be replaced with 'auction' for win notification at auction time. For notifications at display time, please see Display Notification section belowOur loss lurl contains 3 flags you need populate:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&phase=${PHASE}"
${AUCTION_LOSS}
: This should be replaced with ORTB Loss Code.${AUCTION_PRICE}
: This should be replaced with the Clearing Price for the auction in the same unit as our bid (i.e. USD on CPM basis). For loss code 100 case, this could be auction floor price.${PHASE}
: This should be replaced with 'auction' for loss notification at auction time. For notifications at display time, please see Display Notification section belowBelow is a list of different Loss Codes and corresponding Loss Reasons.
Loss Reason | Description | ORTB v2.5 Loss Code |
---|---|---|
Internal error | Internal error (e.g. when we won the auction, but our ad failed to load) | 1 |
Invalid bid response | Bid is invalid (but on-time, not a no-bid, and valid enough that you can extract the nurl) | 3 |
Bid timeout * | Bid response received, but too late for auction cutoff | 2 |
No bid * | No-bids are indicated as HTTP 204 (i.e. no nurl to call), but you may interpret our response as a no-bid (likely an integration issue). You may also request bids for several impressions, and we bid on some but not all. No need to send loss notification for this reason. No bid should be used as loss reason with timeout construction of lurl in cases where lurl is not available or unusable. | 9 |
Bid was Below Auction Floor | Bidding price was below current auction floor. ${AUCTION_PRICE} will be auction floor price or clearing price. | 100 |
Not highest RTB bidder | Another bidder beat us, including synthetic bids (e.g. non-RTB exchanges), if they are entered into the same auction. | 102 |
Lost to a Bid for a PMP Deal | A PMP (tag or traditional waterfall) deal was picked over the bidders in the auction. | 103 |
Inventory didn't materialise | Our bid won the auction, but the impression didn't materialize (e.g. page wasn't long enough to include this slot, or the user exited the app before the cached ad was used.) Not all partners can provide this (it's a non-event), so we will infer it if not provided. | 4902 |
Sent to ad server | Send this if the last touchpoint you have with the decision process is sending our high bid to the ad server. The impression may still be lost through missing line items, the ad server overruling the auction, or the inventory not materializing. | 4900 |
RTB winner not picked by ad server | We won the RTB auction, but the ad server overruled the auction (e.g. direct). | 4903 |
Win | We won the full decision tree, and tag was placed on page (web) or ad object was cached (app). Viewable impression may still not result. | 0 |
For the case of bid timeout or no bid due to unusable bid response (missing/unusable lurl), we provide you with an alternative reporting route. It is the generic nurl which might be called upon without the need to wait for the bid to arrive. The format is as follows:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&auction=${AUCTION_ID}&ortb_loss_code=2"
Note: ${PARTNER_FBID}
, ${APP_FBID}
and ${AUCTION_ID}
should be populated with appropriate values. The table below provides explanation about those values.
Param | Type | Description |
---|---|---|
PARTNER_FBID | Int | Ad auction server id issued by Facebook. Use your app id here if you don't have a dedicated ad auction partner. |
APP_FBID | Int | Facebook-issued Id of the application/business which initiated an auction. |
AUCTION_ID | String | Client-generated id of the auction you used for issuing a bid request. |
We encourage publishers to send display notifications to Facebook bidder when they're about to show the ad. If the bid from Facebook bidder won the auction, use the nurl to send display notifications
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}&phase=${PHASE}"
${AUCTION_PRICE}
: Similar to win notification at auction time, this should be replaced with the Clearing Price for the auction in the same unit as our bid (i.e. USD on CPM basis).${PHASE}
: This should be replaced with 'display' for win notifications at display time. If the bid from Facebook bidding didn't won the auction, use the lurl to send display notifications
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}&phase=${PHASE}"
${AUCTION_LOSS}
: Similar to loss notification at auction time, this should be replaced with ORTB Loss Code.${AUCTION_PRICE}
: Similar to loss notification at auction time, this should be replaced with the Clearing Price for the auction in the same unit as our bid (i.e. USD on CPM basis). For loss code 100 case, this could be auction floor price.${PHASE}
: This should be replaced with 'display' for loss notifications at display time. 執行競標後,我們會建立回應物件,並將這個物件傳回用戶端應用程式。這是 bid_manager.py
範例中建立競價伺服器最終回應的方法:
def create_response(bid_response, placement): """Create response object based on the auction result""" ad_format = placement['ad_format'] platform_name = bid_response['platform_name'] platform_placement_id = None for bidding_source_placement_id in placement[ 'bidding_source_placement_ids' ]: if bidding_source_placement_id['platform_name'] == platform_name: platform_placement_id = bidding_source_placement_id[ 'platform_placement_id' ] if platform_placement_id is None: raise InternalError("Platform placement ID not found!") bid_payload = None if platform_name == 'audience_network': bid_payload = bid_response['response'].adm else: raise InternalError("Invalid platform") return (200, { 'placement_id': placement['placement_id'], 'ad_format': ad_format, 'platform_name': platform_name, 'platform_placement_id': platform_placement_id, 'bid_payload': bid_payload, })
最後,我們的範例伺服器可以建立這則給用戶端的回應,通知用戶端可以使用平台:
{ 'placement_id': string, // Placement identifier for the auction server 'ad_format': string, // Format of the placement 'platform_name': string, // Which platform won the auction, for example 'audience_network' 'platform_placement_id': string, // Placement ID for the platform, for example the placement ID for Audience network 'bid_payload': string, // The JSON string payload for the platform SDK to load the final ad }