Skip to content

Opera Ads iOS SDK - Server Bidding (S2S)

Overview

Server-to-Server (S2S) Bidding, also known as Server-Side Bidding or Header Bidding, allows you to run ad auctions on your own mediation server or integrate with third-party mediation platforms that support server-side auctions.

Benefits of Server Bidding

  • Centralized Auction Logic: Manage bidding on your server
  • Reduced Client Complexity: Less code in your app
  • Flexibility: Easy to add/remove ad networks
  • Cross-Platform Consistency: Same auction logic for iOS and Android
  • Better Analytics: Centralized bid tracking and reporting

How It Works

┌─────────────────────────────────────────────────────────┐
│  1. App requests bid token from Opera Ads SDK           │
│     ┌──────────────┐                                    │
│     │  getBidToken │                                    │
│     └──────┬───────┘                                    │
│            │                                            │
│            ▼                                            │
│     Bid Token (compressed signals)                     │
│            │                                            │
│            ▼                                            │
│  2. Send bid token to your auction server              │
│     ┌──────────────┐                                    │
│     │ POST /auction│                                    │
│     └──────┬───────┘                                    │
│            │                                            │
│            ▼                                            │
│  3. Server runs auction with Opera and other networks  │
│     ┌──────────────┐                                    │
│     │Opera: $1.50  │                                    │
│     │Net A: $1.20  │                                    │
│     │Net B: $1.80  │ ← Winner                           │
│     └──────┬───────┘                                    │
│            │                                            │
│            ▼                                            │
│  4. Server returns bid response (if Opera wins)        │
│     ┌──────────────┐                                    │
│     │Bid Response  │                                    │
│     └──────┬───────┘                                    │
│            │                                            │
│            ▼                                            │
│  5. App loads ad with bid response                     │
│     ┌──────────────┐                                    │
│     │  loadRtbAd   │                                    │
│     └──────────────┘                                    │
└─────────────────────────────────────────────────────────┘

Prerequisites

Before implementing server bidding, ensure you have:

  1. Opera Ads SDK Integration

    • SDK version 2.1.0 or higher
    • Basic integration completed
  2. Server Infrastructure

    • Auction server or third-party mediation platform
    • Ability to process bid tokens
    • API endpoints for auction requests/responses
  3. Understanding of S2S Bidding

    • Bid token format and usage
    • Auction server communication
    • Bid response handling

Implementation Steps

Step 1: Request Bid Token

Request a bid token from the Opera Ads SDK. This token contains encrypted bidding signals.

Swift Example:

swift
import OpAdxSdk

class ServerBiddingManager {

    func requestBidToken(for adFormat: AdFormat, completion: @escaping (String?) -> Void) {
        // Create bid token request
        let request = BidTokenRequest.Builder(adMediation: .custom)  // Or .admob, .applovin, .topon
            .placementId("s14198264979520")
            .adFormat(adFormat)
            .adSize(.BANNER_MREC)  // Optional: For banner ads
            .build()

        // Request bid token
        OpAdxSDK.getBidToken(request) { callback in
            callback.onSuccess { token in
                print("✅ Bid token received: \(token.prefix(50))...")
                completion(token)
            }

            callback.onError { error in
                print("❌ Bid token request failed: \(error.message)")
                completion(nil)
            }
        }
    }
}

Objective-C Example:

objective-c
#import <OpAdxSdk/OpAdxSdk.h>

@implementation ServerBiddingManager

- (void)requestBidTokenForAdFormat:(AdFormat)adFormat
                        completion:(void (^)(NSString * _Nullable))completion {

    // Create bid token request
    OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
        initWithAdMediation:@"custom"];

    [builder placementId:@"s14198264979520"];
    [builder adFormat:[self adFormatString:adFormat]];

    if (adFormat == AdFormatBanner) {
        [builder adSize:@"300x250"];
    }

    OpAdxBidTokenRequest *request = [builder build];

    // Request bid token
    [OpAdxSDK getBidToken:request callback:^(NSString * _Nullable token, OpAdxAdError * _Nullable error) {
        if (token) {
            NSLog(@"✅ Bid token received: %@...", [token substringToIndex:MIN(50, token.length)]);
            completion(token);
        } else {
            NSLog(@"❌ Bid token request failed: %@", error.message);
            completion(nil);
        }
    }];
}

@end

Step 2: Send Bid Token to Server

Send the bid token to your auction server along with other auction parameters.

Swift:

swift
import Foundation

class AuctionClient {

    func sendAuctionRequest(bidToken: String,
                           adFormat: String,
                           placementId: String,
                           completion: @escaping (AuctionResponse?) -> Void) {

        // Prepare auction request
        let requestBody: [String: Any] = [
            "bid_token": bidToken,
            "ad_format": adFormat,
            "placement_id": placementId,
            "device_id": UIDevice.current.identifierForVendor?.uuidString ?? "",
            "app_version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
        ]

        guard let url = URL(string: "https://your-server.com/api/auction"),
              let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) else {
            completion(nil)
            return
        }

        // Send request
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = jsonData
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data,
                  error == nil,
                  let auctionResponse = try? JSONDecoder().decode(AuctionResponse.self, from: data) else {
                completion(nil)
                return
            }

            completion(auctionResponse)
        }.resume()
    }
}

struct AuctionResponse: Codable {
    let winner: String          // "opera" or other network name
    let bidResponse: String?    // Opera bid response (if Opera won)
    let ecpm: Double
    let placementId: String
}

Objective-C:

objective-c
@implementation AuctionClient

- (void)sendAuctionRequestWithBidToken:(NSString *)bidToken
                              adFormat:(NSString *)adFormat
                           placementId:(NSString *)placementId
                            completion:(void (^)(NSDictionary * _Nullable))completion {

    // Prepare auction request
    NSDictionary *requestBody = @{
        @"bid_token": bidToken,
        @"ad_format": adFormat,
        @"placement_id": placementId,
        @"device_id": [UIDevice currentDevice].identifierForVendor.UUIDString ?: @"",
        @"app_version": [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] ?: @""
    };

    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:requestBody
                                                       options:0
                                                         error:&error];

    if (error || !jsonData) {
        completion(nil);
        return;
    }

    // Send request
    NSURL *url = [NSURL URLWithString:@"https://your-server.com/api/auction"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBody = jsonData;
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

    [[[NSURLSession sharedSession] dataTaskWithRequest:request
                                     completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error || !data) {
            completion(nil);
            return;
        }

        NSDictionary *auctionResponse = [NSJSONSerialization JSONObjectWithData:data
                                                                        options:0
                                                                          error:nil];
        completion(auctionResponse);
    }] resume];
}

@end

Step 3: Load Ad with Bid Response

If Opera Ads wins the server auction, load the ad using the bid response.

Swift:

swift
class ServerBiddingManager {

    func handleAuctionResponse(_ response: AuctionResponse) {
        guard response.winner == "opera",
              let bidResponse = response.bidResponse else {
            print("Opera Ads did not win - show other network's ad")
            showOtherNetworkAd()
            return
        }

        print("✅ Opera Ads won with eCPM: $\(response.ecpm)")
        loadOperaAd(with: bidResponse, placementId: response.placementId)
    }

    func loadOperaAd(with bidResponse: String, placementId: String) {
        let interstitialAd = OpAdxInterstitialAd(
            placementId: placementId,
            auctionType: .serverBidding  // ← Important: Use serverBidding
        )

        let loadListener = OpAdxInterstitialAdLoadListenerImp(
            onAdLoaded: { ad in
                print("✅ Opera ad loaded from bid response")
                self.showOperaAd(interstitialAd)
            },
            onAdFailedToLoad: { error in
                print("❌ Opera ad failed to load: \(error.message)")
            }
        )

        // Load ad with bid response
        interstitialAd.load(
            bidResponse: bidResponse,
            listener: loadListener
        )
    }

    func showOperaAd(_ ad: OpAdxInterstitialAd) {
        let interactionListener = OpAdxInterstitialAdInteractionListenerImp(
            onAdClicked: {
                print("Ad clicked")
            },
            onAdDisplayed: {
                print("Ad displayed")
            },
            onAdDismissed: {
                print("Ad dismissed")
            },
            onAdFailedToShow: { error in
                print("Ad failed to show: \(error.message)")
            }
        )

        ad.show(on: self.viewController, listener: interactionListener)
    }
}

Objective-C:

objective-c
@implementation ServerBiddingManager

- (void)handleAuctionResponse:(NSDictionary *)response {
    NSString *winner = response[@"winner"];
    NSString *bidResponse = response[@"bid_response"];

    if (![winner isEqualToString:@"opera"] || !bidResponse) {
        NSLog(@"Opera Ads did not win - show other network's ad");
        [self showOtherNetworkAd];
        return;
    }

    double ecpm = [response[@"ecpm"] doubleValue];
    NSLog(@"✅ Opera Ads won with eCPM: $%.4f", ecpm);

    [self loadOperaAdWithBidResponse:bidResponse placementId:response[@"placement_id"]];
}

- (void)loadOperaAdWithBidResponse:(NSString *)bidResponse placementId:(NSString *)placementId {
    OpAdxInterstitialAdBridge *interstitialAd = [[OpAdxInterstitialAdBridge alloc]
        initWithPlacementId:placementId
        auctionType:AdAuctionTypeServerBidding];  // ← Important

    interstitialAd.delegate = self;

    // Load ad with bid response
    [interstitialAd loadWithBidResponse:bidResponse];
}

#pragma mark - OpAdxInterstitialAdDelegate

- (void)interstitialAdDidLoad:(OpAdxInterstitialAdBridge *)interstitialAd {
    NSLog(@"✅ Opera ad loaded from bid response");
    [self showOperaAd:interstitialAd];
}

- (void)interstitialAd:(OpAdxInterstitialAdBridge *)interstitialAd
      didFailWithError:(OpAdxAdError *)error {
    NSLog(@"❌ Opera ad failed to load: %@", error.message);
}

@end

Ad Format Implementations

Swift:

swift
func loadBannerWithServerBidding(bidResponse: String) {
    let bannerAd = OpAdxBannerAdView()
    bannerAd.setPlacementId("s14170965187264")
    bannerAd.setAdSize(.BANNER_MREC)

    let listener = OpAdxBannerAdListenerImp(
        onAdLoaded: { [weak self] bannerInfo in
            print("Banner loaded from bid response")
            self?.showBanner(bannerAd)
        },
        onAdFailedToLoad: { error in
            print("Banner failed: \(error.message)")
        },
        onAdImpression: {
            print("Banner impression")
        },
        onAdClicked: {
            print("Banner clicked")
        }
    )

    // Load banner with bid response
    bannerAd.loadRtbAd(bidResponse: bidResponse, listener: listener)
}

func showBanner(_ banner: OpAdxBannerAdView) {
    view.addSubview(banner)
    banner.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        banner.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        banner.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        banner.widthAnchor.constraint(equalToConstant: 300),
        banner.heightAnchor.constraint(equalToConstant: 250)
    ])
}

Objective-C:

objective-c
- (void)loadBannerWithServerBidding:(NSString *)bidResponse {
    OpAdxBannerAdBridge *bannerAd = [[OpAdxBannerAdBridge alloc]
        initWithPlacementId:@"s14170965187264"
        adSize:OpAdxAdSize.BANNER_MREC];

    bannerAd.delegate = self;

    // Load banner with bid response
    [bannerAd loadRtbAdWithBidResponse:bidResponse];
}

- (void)bannerAdDidLoad:(OpAdxBannerAdBridge *)bannerAd {
    NSLog(@"Banner loaded from bid response");
    [self showBanner:bannerAd];
}

Native Ads

Swift:

swift
func loadNativeWithServerBidding(bidResponse: String) {
    let nativeAd = OpAdxNativeAd(
        placementId: "s14198263063424",
        auctionType: .serverBidding
    )

    let listener = OpAdxNativeAdListenerImp(
        onAdLoaded: { [weak self] ad in
            guard let self = self, let nativeAd = ad as? OpAdxNativeAd else { return }
            print("Native ad loaded from bid response")
            self.renderNativeAd(nativeAd)
        },
        onAdFailedToLoad: { error in
            print("Native ad failed: \(error.message)")
        },
        onAdImpression: {
            print("Native ad impression")
        },
        onAdClicked: {
            print("Native ad clicked")
        }
    )

    // Load native ad with bid response
    nativeAd.loadRtb(bidResponse: bidResponse, listener: listener)
}

func renderNativeAd(_ ad: OpAdxNativeAd) {
    let nativeAdView = OpAdxNativeAdView(frame: CGRect(x: 0, y: 0, width: 300, height: 400))
    nativeAdView.configure(with: ad)

    let rootView = OpAdxNativeAdRootView(root: nativeAdView)
    ad.registerInteractionViews(
        container: rootView,
        interactionViews: nativeAdView.interactionViews,
        adChoicePosition: .topRight
    )

    adContainer.addSubview(nativeAdView)
}

Rewarded Ads

Swift:

swift
func loadRewardedWithServerBidding(bidResponse: String) {
    let rewardedAd = OpAdxRewardedAd(
        placementId: "s14198592226752",
        auctionType: .serverBidding
    )

    let loadListener = OpAdxRewardedAdLoadListenerImp(
        onAdLoaded: { [weak self] ad in
            print("Rewarded ad loaded from bid response")
            self?.showRewardedAd(rewardedAd)
        },
        onAdFailedToLoad: { error in
            print("Rewarded ad failed: \(error.message)")
        }
    )

    // Load rewarded ad with bid response
    rewardedAd.loadRtb(bidResponse: bidResponse, listener: loadListener)
}

App Open Ads

Swift:

swift
func loadAppOpenWithServerBidding(bidResponse: String) {
    let appOpenAd = OpAdxAppOpenAd(
        placementId: "s14496438551808",
        auctionType: .serverBidding
    )

    let loadListener = OpAdxAppOpenAdLoadListenerImp(
        onAdLoaded: { [weak self] ad in
            print("App open ad loaded from bid response")
            self?.showAppOpenAd(appOpenAd)
        },
        onAdFailedToLoad: { error in
            print("App open ad failed: \(error.message)")
        }
    )

    // Load app open ad with bid response
    appOpenAd.loadRtb(bidResponse: bidResponse, listener: loadListener)
}

Bid Token Request Configuration

Ad Mediation Types

Specify your mediation platform when requesting bid tokens:

Swift:

swift
// For custom mediation server
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

// For AdMob mediation
let request = BidTokenRequest.Builder(adMediation: .admob)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

// For AppLovin MAX mediation
let request = BidTokenRequest.Builder(adMediation: .applovin)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

// For TopOn mediation
let request = BidTokenRequest.Builder(adMediation: .topon)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

Objective-C:

objective-c
// For custom mediation server
OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
    initWithAdMediation:@"custom"];

// For AdMob mediation
OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
    initWithAdMediation:@"admob"];

// For AppLovin MAX mediation
OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
    initWithAdMediation:@"applovin"];

// For TopOn mediation
OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
    initWithAdMediation:@"topon"];

Ad Formats

Specify the ad format in your bid token request:

Swift:

swift
// Banner
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14170965187264")
    .adFormat(.banner)
    .adSize(.BANNER_MREC)  // Optional: Specify banner size
    .build()

// Interstitial
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

// Rewarded
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14198592226752")
    .adFormat(.rewarded)
    .build()

// Native
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14198263063424")
    .adFormat(.native)
    .build()

// App Open
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14496438551808")
    .adFormat(.appOpen)
    .build()

For banner ads, specify the ad size:

Swift:

swift
// Medium Rectangle (300x250)
.adSize(.BANNER_MREC)

// Standard Banner (320x50)
.adSize(.BANNER)

// Large Banner (320x100)
.adSize(.BANNER_LARGE)

// Leaderboard (728x90)
.adSize(.BANNER_LEADERBOARD)

Complete Workflow Example

Here's a complete implementation of server bidding:

Swift:

swift
import OpAdxSdk

class CompleteServerBiddingExample {

    // MARK: - Step 1: Request Bid Token

    func startServerBidding(for adFormat: AdFormat, placementId: String) {
        print("Step 1: Requesting bid token...")

        let request = BidTokenRequest.Builder(adMediation: .custom)
            .placementId(placementId)
            .adFormat(adFormat)
            .build()

        OpAdxSDK.getBidToken(request) { callback in
            callback.onSuccess { [weak self] token in
                print("✅ Bid token received")
                self?.sendToAuctionServer(
                    bidToken: token,
                    adFormat: adFormat,
                    placementId: placementId
                )
            }

            callback.onError { error in
                print("❌ Bid token failed: \(error.message)")
            }
        }
    }

    // MARK: - Step 2: Send to Auction Server

    func sendToAuctionServer(bidToken: String, adFormat: AdFormat, placementId: String) {
        print("Step 2: Sending to auction server...")

        let requestBody: [String: Any] = [
            "bid_token": bidToken,
            "ad_format": adFormat.rawValue,
            "placement_id": placementId
        ]

        guard let url = URL(string: "https://your-server.com/api/auction"),
              let jsonData = try? JSONSerialization.data(withJSONObject: requestBody) else {
            return
        }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = jsonData
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
            guard let data = data,
                  error == nil,
                  let auctionResponse = try? JSONDecoder().decode(AuctionResponse.self, from: data) else {
                print("❌ Auction request failed")
                return
            }

            DispatchQueue.main.async {
                self?.handleAuctionResponse(auctionResponse, adFormat: adFormat)
            }
        }.resume()
    }

    // MARK: - Step 3: Handle Auction Response

    func handleAuctionResponse(_ response: AuctionResponse, adFormat: AdFormat) {
        print("Step 3: Handling auction response...")
        print("Winner: \(response.winner) with eCPM: $\(response.ecpm)")

        guard response.winner == "opera",
              let bidResponse = response.bidResponse else {
            print("Opera Ads did not win - showing other network")
            return
        }

        loadOperaAd(
            bidResponse: bidResponse,
            adFormat: adFormat,
            placementId: response.placementId
        )
    }

    // MARK: - Step 4: Load Ad with Bid Response

    func loadOperaAd(bidResponse: String, adFormat: AdFormat, placementId: String) {
        print("Step 4: Loading Opera ad with bid response...")

        switch adFormat {
        case .banner:
            loadBanner(bidResponse: bidResponse, placementId: placementId)
        case .interstitial:
            loadInterstitial(bidResponse: bidResponse, placementId: placementId)
        case .rewarded:
            loadRewarded(bidResponse: bidResponse, placementId: placementId)
        case .native:
            loadNative(bidResponse: bidResponse, placementId: placementId)
        case .appOpen:
            loadAppOpen(bidResponse: bidResponse, placementId: placementId)
        }
    }

    func loadInterstitial(bidResponse: String, placementId: String) {
        let ad = OpAdxInterstitialAd(
            placementId: placementId,
            auctionType: .serverBidding
        )

        let listener = OpAdxInterstitialAdLoadListenerImp(
            onAdLoaded: { [weak self] _ in
                print("✅ Interstitial loaded - ready to show")
                self?.showInterstitial(ad)
            },
            onAdFailedToLoad: { error in
                print("❌ Interstitial failed: \(error.message)")
            }
        )

        ad.load(bidResponse: bidResponse, listener: listener)
    }

    func showInterstitial(_ ad: OpAdxInterstitialAd) {
        let listener = OpAdxInterstitialAdInteractionListenerImp(
            onAdClicked: {
                print("Interstitial clicked")
            },
            onAdDisplayed: {
                print("Interstitial displayed")
            },
            onAdDismissed: {
                print("Interstitial dismissed")
            },
            onAdFailedToShow: { error in
                print("Failed to show: \(error.message)")
            }
        )

        guard let viewController = UIApplication.shared.windows.first?.rootViewController else {
            return
        }

        ad.show(on: viewController, listener: listener)
    }
}

// Response model
struct AuctionResponse: Codable {
    let winner: String
    let bidResponse: String?
    let ecpm: Double
    let placementId: String
}

Server-Side Implementation Guide

Auction Server Requirements

Your auction server should:

  1. Receive Bid Token

    json
    POST /api/auction
    {
      "bid_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "ad_format": "interstitial",
      "placement_id": "s14198264979520",
      "device_id": "ABC123...",
      "app_version": "1.0.0"
    }
  2. Forward to Opera Ads Auction Endpoint

    POST https://auction.opera.com/v1/bid
    Headers:
      Authorization: Bearer YOUR_API_KEY
      Content-Type: application/json
    
    Body:
    {
      "bid_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "floor_price": 0.50
    }
  3. Receive Opera Bid Response

    json
    {
      "status": "success",
      "ecpm": 1.50,
      "bid_response": "base64_encoded_bid_response_data...",
      "currency": "USD"
    }
  4. Run Auction with Other Networks

    javascript
    const bids = [
      { network: "opera", ecpm: 1.50, bidResponse: "..." },
      { network: "network_a", ecpm: 1.20, bidResponse: "..." },
      { network: "network_b", ecpm: 1.80, bidResponse: "..." }
    ];
    
    const winner = bids.sort((a, b) => b.ecpm - a.ecpm)[0];
  5. Return Winner to App

    json
    {
      "winner": "opera",
      "bid_response": "base64_encoded_bid_response_data...",
      "ecpm": 1.50,
      "placement_id": "s14198264979520"
    }

Sample Server Code (Node.js)

javascript
const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

app.post('/api/auction', async (req, res) => {
  const { bid_token, ad_format, placement_id } = req.body;

  try {
    // Step 1: Get Opera Ads bid
    const operaBid = await axios.post('https://auction.opera.com/v1/bid', {
      bid_token: bid_token,
      floor_price: 0.50
    }, {
      headers: {
        'Authorization': `Bearer ${process.env.OPERA_API_KEY}`,
        'Content-Type': 'application/json'
      }
    });

    // Step 2: Get bids from other networks (parallel)
    const [networkA, networkB] = await Promise.all([
      getNetworkABid(ad_format, placement_id),
      getNetworkBBid(ad_format, placement_id)
    ]);

    // Step 3: Run auction
    const bids = [
      { network: 'opera', ecpm: operaBid.data.ecpm, bidResponse: operaBid.data.bid_response },
      { network: 'network_a', ecpm: networkA.ecpm, bidResponse: networkA.bidResponse },
      { network: 'network_b', ecpm: networkB.ecpm, bidResponse: networkB.bidResponse }
    ];

    const winner = bids.sort((a, b) => b.ecpm - a.ecpm)[0];

    // Step 4: Return winner
    res.json({
      winner: winner.network,
      bid_response: winner.bidResponse,
      ecpm: winner.ecpm,
      placement_id: placement_id
    });

  } catch (error) {
    console.error('Auction error:', error);
    res.status(500).json({ error: 'Auction failed' });
  }
});

app.listen(3000, () => console.log('Auction server running on port 3000'));

Best Practices

1. Caching Bid Tokens

Don't cache bid tokens - request fresh tokens for each auction:

swift
// ✅ Good: Fresh token for each auction
func loadAd() {
    requestBidToken { token in
        self.sendToServer(token)
    }
}

// ❌ Bad: Reusing old tokens
var cachedToken: String?
func loadAd() {
    if let token = cachedToken {
        sendToServer(token)  // May be expired!
    }
}

2. Timeout Handling

Set reasonable timeouts for auction requests:

swift
var request = URLRequest(url: url)
request.timeoutInterval = 5.0  // 5 seconds

URLSession.shared.dataTask(with: request) { data, response, error in
    if let error = error as NSError?, error.code == NSURLErrorTimedOut {
        print("Auction timeout - using fallback")
        self.showFallbackAd()
    }
}.resume()

3. Error Handling

Always handle auction failures gracefully:

swift
func handleAuctionResponse(_ response: AuctionResponse?) {
    guard let response = response else {
        // Auction failed - show fallback
        showFallbackAd()
        return
    }

    if response.winner == "opera" {
        loadOperaAd(bidResponse: response.bidResponse)
    } else {
        showOtherNetworkAd()
    }
}

4. Thread Safety

Always update UI on main thread:

swift
URLSession.shared.dataTask(with: request) { data, response, error in
    // Parse response on background thread
    let auctionResponse = parseResponse(data)

    // Update UI on main thread
    DispatchQueue.main.async {
        self.handleAuctionResponse(auctionResponse)
    }
}.resume()

Troubleshooting

Issue: Bid Token Request Fails

Cause: SDK not initialized or invalid parameters

Solution:

swift
// Ensure SDK is initialized first
OpAdxSDK.initialize(withConfig: config) {
    // Now request bid token
    self.requestBidToken()
}

// Verify request parameters
let request = BidTokenRequest.Builder(adMediation: .custom)
    .placementId("s14198264979520")  // Valid placement ID
    .adFormat(.interstitial)         // Valid ad format
    .build()

Issue: Bid Response Loading Fails

Cause: Invalid bid response or expired token

Solution:

  • Don't cache bid responses
  • Request fresh bid token for each auction
  • Check bid response format from server
  • Verify placement ID matches

Issue: Low Fill Rate

Cause: Floor price too high or limited demand

Solution:

  • Lower floor price on server
  • Check bid token includes correct signals
  • Verify placement ID is active
  • Test with different geos

Support


Document Version: 2.9.0 Last Updated: 2026-03-24 SDK Version: 2.9.0