Header Bidding Basics#
This notebook implements a Header Bidding simulation using Flask. It demonstrates how a publisher can request bids from multiple demand partners (SSPs/Exchanges) in parallel, handle timeouts, and select a winning bid.
How to Run This Code#
Install Requirements: Ensure you have Flask installed (
pip install flask).Run All Cells: Execute all cells in this notebook. The final cell starts the Flask server.
Access the Demo: Once the server is running, open your web browser and go to:
http://127.0.0.1:5002/test-hb for the interactive demo.
http://127.0.0.1:5002/ for the main index.
Stop the Server: To stop the server, click the “Stop” button in the notebook toolbar or interrupt the kernel.
# Import necessary libraries
from flask import Flask, jsonify, request, render_template_string
from datetime import datetime
import json
from typing import Dict, List
import uuid
import time
import asyncio
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
# Initialize Flask application
app = Flask(__name__)
# Configuration of Demand Partners (SSPs/Exchanges)
# Each partner has specific targeting criteria, timeouts, and floor prices
demand_partners = [
{
"id": "partner_1",
"name": "PremiumSSP",
"endpoint": "/bid/premium-ssp",
"timeout_ms": 1500,
"active": True,
"floor_price": 1.0, # Minimum bid price
"targeting": {
"countries": ["US", "CA", "UK"],
"devices": ["desktop", "mobile"],
"interests": ["finance", "technology"]
}
},
{
"id": "partner_2",
"name": "GlobalExchange",
"endpoint": "/bid/global-exchange",
"timeout_ms": 1200,
"active": True,
"floor_price": 0.5,
"targeting": {
"countries": ["US", "DE", "FR", "UK"],
"devices": ["desktop", "mobile", "tablet"],
"interests": ["general", "news", "sports"]
}
},
{
"id": "partner_3",
"name": "MobileFirst",
"endpoint": "/bid/mobile-first",
"timeout_ms": 800,
"active": True,
"floor_price": 2.0,
"targeting": {
"countries": ["US"],
"devices": ["mobile"],
"interests": ["gaming", "apps", "mobile"]
}
},
{
"id": "partner_4",
"name": "VideoSSP",
"endpoint": "/bid/video-ssp",
"timeout_ms": 2000,
"active": True,
"floor_price": 3.0,
"targeting": {
"countries": ["US", "CA"],
"devices": ["desktop", "mobile"],
"interests": ["video", "entertainment"]
}
}
]
class HeaderBiddingAuction:
"""Class handling header bidding auction logic"""
def __init__(self, timeout_ms: int = 2000):
self.timeout_ms = timeout_ms
self.executor = ThreadPoolExecutor(max_workers=10)
def matches_targeting(self, partner: Dict, request_data: Dict) -> bool:
"""Check if request matches partner's targeting criteria"""
targeting = partner['targeting']
# Check country targeting
if request_data.get('country') not in targeting['countries']:
return False
# Check device targeting
if request_data.get('device') not in targeting['devices']:
return False
# Check interest overlap
user_interests = set(request_data.get('interests', []))
partner_interests = set(targeting['interests'])
if not user_interests & partner_interests: # No overlap
return False
return True
def simulate_partner_bid(self, partner: Dict, request_data: Dict) -> Dict:
"""Simulate a bid request to a demand partner"""
start_time = time.time()
try:
# Simulate network latency and processing time
# Uses partner properties to generate deterministic but varied delays
processing_time = min(partner['timeout_ms'] / 1000.0, 0.1 + (hash(partner['id']) % 100) / 1000.0)
time.sleep(processing_time)
# Check if partner can bid on this request
if not self.matches_targeting(partner, request_data):
return {
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "no_bid",
"reason": "targeting_mismatch",
"response_time_ms": (time.time() - start_time) * 1000
}
# Calculate bid price based on targeting match and demand
base_bid = partner['floor_price']
# Add premium for better targeting match
user_interests = set(request_data.get('interests', []))
partner_interests = set(partner['targeting']['interests'])
interest_match = len(user_interests & partner_interests)
bid_multiplier = 1.0 + (interest_match * 0.2) # 20% per matching interest
# Add randomness to simulate market dynamics
market_factor = 0.8 + (hash(str(request_data) + partner['id']) % 100) / 250.0 # 0.8 to 1.2x
final_bid = base_bid * bid_multiplier * market_factor
# Simulate occasional bid failures
if hash(partner['id'] + str(time.time())) % 20 == 0: # 5% failure rate
return {
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "error",
"reason": "internal_error",
"response_time_ms": (time.time() - start_time) * 1000
}
return {
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "bid",
"bid_cpm": round(final_bid, 2),
"ad_markup": f"<div>Ad from {partner['name']} - CPM: ${final_bid:.2f}</div>",
"creative_id": f"creative_{partner['id']}_{int(time.time())}",
"response_time_ms": round((time.time() - start_time) * 1000, 2)
}
except Exception as e:
return {
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "timeout",
"reason": str(e),
"response_time_ms": (time.time() - start_time) * 1000
}
def run_header_bidding_auction(self, request_data: Dict) -> Dict:
"""Run parallel header bidding auction with timeout"""
auction_start = time.time()
# Filter active partners
active_partners = [p for p in demand_partners if p['active']]
# Submit bid requests to all partners in parallel
future_to_partner = {
self.executor.submit(self.simulate_partner_bid, partner, request_data): partner
for partner in active_partners
}
bids = []
timeouts = []
errors = []
# Collect responses with timeout
for future in as_completed(future_to_partner, timeout=self.timeout_ms/1000.0):
try:
result = future.result()
if result['status'] == 'bid':
bids.append(result)
elif result['status'] == 'timeout':
timeouts.append(result)
else:
errors.append(result)
except Exception as e:
partner = future_to_partner[future]
errors.append({
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "exception",
"reason": str(e),
"response_time_ms": (time.time() - auction_start) * 1000
})
# Handle any remaining futures that didn't complete
for future in future_to_partner:
if not future.done():
future.cancel()
partner = future_to_partner[future]
timeouts.append({
"partner_id": partner['id'],
"partner_name": partner['name'],
"status": "timeout",
"reason": "auction_timeout",
"response_time_ms": self.timeout_ms
})
# Select winning bid (highest CPM)
winning_bid = None
if bids:
winning_bid = max(bids, key=lambda x: x['bid_cpm'])
total_auction_time = (time.time() - auction_start) * 1000
return {
"auction_id": str(uuid.uuid4()),
"timestamp": datetime.utcnow().isoformat(),
"total_auction_time_ms": round(total_auction_time, 2),
"partners_called": len(active_partners),
"successful_bids": len(bids),
"timeouts": len(timeouts),
"errors": len(errors),
"winning_bid": winning_bid,
"all_bids": bids,
"failed_responses": timeouts + errors
}
# Initialize global auction manager
auction_manager = HeaderBiddingAuction()
# API Routes
@app.route('/header-bidding', methods=['POST'])
def run_header_bidding():
"""
Main header bidding endpoint that runs parallel auction
Expected JSON payload:
{
"ad_unit_id": "banner_300x250_1",
"size": "300x250",
"country": "US",
"device": "desktop",
"interests": ["technology", "finance"],
"user_id": "user_123",
"page_url": "https://example.com/article",
"timeout_ms": 2000
}
"""
try:
data = request.get_json()
# Set custom timeout if provided
timeout_ms = data.get('timeout_ms', 2000)
auction_manager.timeout_ms = timeout_ms
# Prepare request data
request_data = {
'ad_unit_id': data.get('ad_unit_id', 'default_unit'),
'size': data.get('size', '300x250'),
'country': data.get('country', 'US'),
'device': data.get('device', 'desktop'),
'interests': data.get('interests', []),
'user_id': data.get('user_id', 'anonymous'),
'page_url': data.get('page_url', ''),
}
# Run the header bidding auction
auction_result = auction_manager.run_header_bidding_auction(request_data)
return jsonify(auction_result)
except Exception as e:
return jsonify({
"error": "header_bidding_failed",
"message": str(e),
"timestamp": datetime.utcnow().isoformat()
}), 500
@app.route('/partners', methods=['GET'])
def get_demand_partners():
"""Get list of configured demand partners"""
return jsonify({
"partners": demand_partners,
"total_partners": len(demand_partners),
"active_partners": len([p for p in demand_partners if p['active']])
})
@app.route('/partner/<partner_id>/toggle', methods=['POST'])
def toggle_partner(partner_id):
"""Enable/disable a demand partner"""
partner = next((p for p in demand_partners if p['id'] == partner_id), None)
if not partner:
return jsonify({"error": "partner_not_found"}), 404
partner['active'] = not partner['active']
return jsonify({
"partner_id": partner_id,
"partner_name": partner['name'],
"active": partner['active']
})
# Interactive Test Page
@app.route('/test-hb')
def test_header_bidding_page():
"""Render a test page for header bidding"""
html_template = """
<!DOCTYPE html>
<html>
<head>
<title>Header Bidding Test</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.ad-slot { border: 2px dashed #ccc; padding: 20px; margin: 20px 0; text-align: center; }
.controls { background: #f5f5f5; padding: 20px; margin: 20px 0; }
.results { background: #e8f4f8; padding: 20px; margin: 20px 0; }
button { padding: 10px 20px; margin: 5px; }
input, select { margin: 5px; padding: 5px; }
.bid-result { border: 1px solid #ddd; padding: 10px; margin: 5px 0; }
.winning-bid { background: #d4edda; border-color: #c3e6cb; }
</style>
</head>
<body>
<h1>Header Bidding Demo</h1>
<div class="controls">
<h3>Auction Parameters</h3>
<label>Country:
<select id="country">
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="DE">Germany</option>
<option value="CA">Canada</option>
</select>
</label><br>
<label>Device:
<select id="device">
<option value="desktop">Desktop</option>
<option value="mobile">Mobile</option>
<option value="tablet">Tablet</option>
</select>
</label><br>
<label>Interests (comma-separated):
<input type="text" id="interests" value="technology,finance" placeholder="technology,finance">
</label><br>
<label>Timeout (ms):
<input type="number" id="timeout" value="2000" min="500" max="5000">
</label><br>
<button onclick="runHeaderBidding()">Run Header Bidding Auction</button>
<button onclick="clearResults()">Clear Results</button>
</div>
<div class="ad-slot" id="ad-slot">
<p>Ad Slot (300x250)</p>
<p>Click "Run Header Bidding Auction" to see results</p>
</div>
<div class="results" id="results" style="display:none;">
<h3>Auction Results</h3>
<div id="auction-summary"></div>
<div id="bid-details"></div>
</div>
<script>
async function runHeaderBidding() {
const country = document.getElementById('country').value;
const device = document.getElementById('device').value;
const interests = document.getElementById('interests').value.split(',').map(s => s.trim());
const timeout = parseInt(document.getElementById('timeout').value);
const payload = {
ad_unit_id: 'test_banner_300x250',
size: '300x250',
country: country,
device: device,
interests: interests,
user_id: 'test_user_' + Date.now(),
page_url: window.location.href,
timeout_ms: timeout
};
try {
document.getElementById('ad-slot').innerHTML = '<p>Running auction...</p>';
const response = await fetch('/header-bidding', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
displayResults(result);
} catch (error) {
document.getElementById('ad-slot').innerHTML = '<p>Error: ' + error.message + '</p>';
}
}
function displayResults(result) {
const resultsDiv = document.getElementById('results');
const summaryDiv = document.getElementById('auction-summary');
const detailsDiv = document.getElementById('bid-details');
// Show auction summary
summaryDiv.innerHTML = `
<p><strong>Auction ID:</strong> ${result.auction_id}</p>
<p><strong>Total Time:</strong> ${result.total_auction_time_ms}ms</p>
<p><strong>Partners Called:</strong> ${result.partners_called}</p>
<p><strong>Successful Bids:</strong> ${result.successful_bids}</p>
<p><strong>Timeouts:</strong> ${result.timeouts}</p>
<p><strong>Errors:</strong> ${result.errors}</p>
`;
// Show winning bid in ad slot
if (result.winning_bid) {
document.getElementById('ad-slot').innerHTML = `
<h4>WINNING AD</h4>
<p><strong>${result.winning_bid.partner_name}</strong></p>
<p>CPM: $${result.winning_bid.bid_cpm}</p>
<p>Response Time: ${result.winning_bid.response_time_ms}ms</p>
<div style="background: #fff; border: 1px solid #ddd; padding: 10px;">
${result.winning_bid.ad_markup}
</div>
`;
} else {
document.getElementById('ad-slot').innerHTML = '<p>No winning bid - showing fallback ad</p>';
}
// Show all bid details
let bidsHtml = '<h4>All Bid Responses:</h4>';
result.all_bids.forEach(bid => {
const isWinner = result.winning_bid && bid.partner_id === result.winning_bid.partner_id;
bidsHtml += `
<div class="bid-result ${isWinner ? 'winning-bid' : ''}">
<strong>${bid.partner_name}</strong> ${isWinner ? '(WINNER)' : ''}
<br>CPM: $${bid.bid_cpm} | Response: ${bid.response_time_ms}ms
</div>
`;
});
result.failed_responses.forEach(failed => {
bidsHtml += `
<div class="bid-result" style="background: #f8d7da;">
<strong>${failed.partner_name}</strong> - ${failed.status.toUpperCase()}
<br>Reason: ${failed.reason} | Time: ${failed.response_time_ms}ms
</div>
`;
});
detailsDiv.innerHTML = bidsHtml;
resultsDiv.style.display = 'block';
}
function clearResults() {
document.getElementById('results').style.display = 'none';
document.getElementById('ad-slot').innerHTML = `
<p>Ad Slot (300x250)</p>
<p>Click "Run Header Bidding Auction" to see results</p>
`;
}
</script>
</body>
</html>
"""
return render_template_string(html_template)
@app.route('/')
def index():
"""Main index page"""
return '''
<h1>Header Bidding Server</h1>
<p>Available endpoints:</p>
<ul>
<li><a href="/test-hb">Test Header Bidding (Interactive Demo)</a></li>
<li><strong>POST /header-bidding</strong> - Run header bidding auction</li>
<li><a href="/partners">GET /partners</a> - View demand partners</li>
<li><strong>POST /partner/<id>/toggle</strong> - Enable/disable partner</li>
</ul>
'''
# Run the Flask application
if __name__ == '__main__':
# Run on port 5002 to avoid conflict with other examples
# use_reloader=False is required for running Flask in Jupyter notebooks
app.run(port=5002, debug=True, use_reloader=False)