Этот документ обновлен.
Перевод (Русский) еще не готов.
Последнее обновление (английский): 21 мая
Последнее обновление (Русский): 26 ноя 2018 г.

Server-to-Server: Auction Server Setup Guide

In-house mediation is not publicly available

In-house bidding with Audience Network is currently in Closed Beta and is not publicly available. We'll provide further updates if this changes.

As an alternative, you can access Audience Network Bidding through one of the mediation platforms we partner with.

This guide describes how you can build an in-house auction server. In the step-by-step guide below, we will be using the example of bidding on an interstitial ad. Make sure that you are already familiar with using Audience Network interstitial ads. Bidding also supports Native, Banner, Interstitial, In-stream Video and Rewarded Video formats. You can check our supported ad formats) below to integrate a different ad format.

Prerequisites

The sample code we provide is only for demonstrating how the bidding API works and how you can integrate it with your in-house auction server. The sample auction server is written in Python with Flask, with minimal stability and security considerations for the simplicity of the source code.


Auction Server Setup Steps

Step 1: Defining the platforms on your server

Step 2: Generating ORTB Requests from your server

Step 3: Executing ORTB Requests from your server

Step 4: Running the auction on your server

Step 5: Firing Win/Loss/Timeout notification

Step 6: Delivering the winner back to your client side

Auction Server Setup Steps

Step 1: Defining the platforms on your server

The ORTB field imp.tagid holds the Audience Network identifier for the inventory. Inventory is represented in Audience Network as ID.

The ORTB request sent to Audience Network bidding endpoint must contain both device and app information. Here is a sample payload in ORTB request:

{
    // 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,
}
Parameter Type Usage

${PLATFORM_ID}

string

If you are a mediation partner, this is the Partner ID provided by your Facebook contact.

If you are a publisher integrating an in-house solution, this is the App ID that you are sending the bid request for. This makes up the first part of the placement ID, before the underscore.

${AUTHENTICATION_ID}

string

Authentication token to validate the originator of the request. Refer to this page.

${AN_SECURITY_APP_MY_SECURITY_APP_ID}

string

Security app id used to generate authentication_id, Refer to this page.

${VERSION_NUMBER}

string

Version of the integration. Max 16 characters, generated by publisher.

The above ORTB request payload is required by Audience Network. However, on your own auction server, actually you can define some of the app specs, such as app ID, placement ID, and some other parameters in the server configuration; then your client side app only needs to send simplified request. Your server can loop up the placement information and construct the final ORTB request. Below is the sample auction server settings:

{
"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}"
}
]
}
]
}
]
}
Parameter Usage

${PLATFORM_ID}

If you are a mediation partner, this is the Partner ID provided by your Facebook contact.

If you are a publisher integrating an in-house solution, this is the App ID that you are sending the bid request for. This makes up the first part of the placement ID, before the underscore.

With above auction server settings, the client side app only needs to send simplified request with "app_id":"101" and "placement_id":"1" to your server; then your server will be able to look up full placement information, such as placement_id, ad_format, platform_name, etc. Here is a simplified sample request payload from your client side:

{
// 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',
},
}

Once the simplified request is sent to your auction server, you should be able to look up the full placement, device and app information by app_id and placement_id in your auction server settings. Here is sample lookup result:

{
    "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}"
    }]
}

Step 2: Generating ORTB Requests from your server

After the auction server receives the auction request, it needs to construct the bid requests for each of demand sources. Here is the endpoint in app.py to receive the auction request:

@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'
)

In bid_manager.py, the auction request from the client contains the app_id and placement_id which will be used to look up for the full placement information in the server settings.

# 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)

Then in audience_network.py, the function get_bid_request will generate final ORTB request for Audience Network based on full placement information.

The ORTB field imp.tagid holds the Audience Network identifier for the inventory. Inventory is represented in Audience Network as placement ID.

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: баннеры, нативную (или нативную баннерную) рекламу, видео (с вознаграждением или In-Stream) и межстраничную рекламу. Примечание. Объекты баннеров, нативной рекламы и видео являются взаимоисключающими, однако один из этих объектов обязателен.

Список поддерживаемых в запросе на рекламу форматов рекламы:

Формат рекламы Параметры в запросе на рекламу

Нативная

{'id': ${AUCTION_ID}, 'native': { 'h': -1, 'w': -1 }, 'tagid': ${PLACEMENT_ID}}

Нативная баннерная

{'id': ${AUCTION_ID}, 'native': { 'h': -1, 'w': -1 }, 'tagid': ${PLACEMENT_ID}}

Межстраничная

{'id': ${AUCTION_ID}, 'banner': { 'h': 0, 'w': 0 }, 'tagid': ${PLACEMENT_ID}, 'instl': 1}

Видео с вознаграждением

{'id': ${AUCTION_ID}, 'video': { 'h': 0, 'w': 0, 'ext': { 'videotype': 'rewarded' } }, 'tagid': ${PLACEMENT_ID}}

Межстраничная с вознаграждением

{'id': ${AUCTION_ID}, 'video': { 'h': 0, 'w': 0, 'ext': { 'videotype': 'rewarded_interstitial' } }, 'tagid': ${PLACEMENT_ID}}

Баннер, высота 50

{'id': ${AUCTION_ID}, 'banner': { 'h': 50, 'w': -1 }, 'tagid': ${PLACEMENT_ID}}

Баннер, высота 250 *

{'id': ${AUCTION_ID}, 'banner': { 'h': 250, 'w': -1 }, 'tagid': ${PLACEMENT_ID}}

* Баннер или место размещения "Средний прямоугольник" для рекламы этого формата можно создать в Monetization Manager.

Step 3: Executing ORTB Requests from your server

After the ORTB request object is created above, we can send the request to the Audience Network endpoint at https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb using HTTP request, using post and Content-Type: application/json.

In bid_manager.py, once it collects all bid requests for each platform, it will call exec_bid_requests for each platform:

# 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))

The following HTTP headers (on both bids and no-bids) in the response will be set that contain useful information for troubleshooting and should be logged on the auction server:

  • X-FB-AN-Request-ID: The Request ID is needed for Audience Network to debug a specific request. Please capture it whenever asking for support.
  • X-FB-AN-Errors: A list of errors encountered, useful to understand reasons for no-bids.
  • X-FB-Debug: Some debug information about this request that you can send to your account representative at Audience Network for troubleshooting.

Note: in order to minimize the latency, you are required to add X-FB-Pool-Routing-Token header into your bid request.

  • X-FB-Pool-Routing-Token: This token is used for routing the request to our nearest data center, and its value is same as user.buyeruid.

In audience_network.py, the ORTB request will be sent to Audience Network, and correspondingly bid response will be delivered to the auction server:

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)

You can pass a binary gzip-compressed request body if you provide a Content-Encoding:gzip header to your request.

Step 4: Running the auction on your server

Now we already have bid responses from different platform (network). In bid_manager.py, the function get_bid_response will be responsible for comparing the filled bid responses and decide on the highest bid - Winner.

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

Because Audience Network is the only bidder in our sample, the run auction method simply compares the returned bid with some price value and decide if it wins the auction. As a result, if the bid returned from Audience Network is higher than 1 dollar, we respond as Audience Network has won the bid, otherwise we treat it as it lost the auction.

Step 5: Firing Win/Loss/Billable/Timeout notification

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.

Win Notification

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 below

Loss Notification

Our 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 below

Below is a list of different Loss Codes and corresponding Loss Reasons.

Loss ReasonDescriptionORTB 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

Timeout Notification

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.

ParamTypeDescription

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.

Display Notification

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.

Step 6: Delivering the winner back to your client side

After running the auction, we create the response object and return it to the client app. Here is the method in the sample in bid_manager.py that creates the final response of the auction server:

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,
})

Finally our sample server can create this response to the client to notify which platform to use:

{
'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
}