Skip to content

Opera Ads iOS SDK - Server Bidding (S2S)

Which integration is this page for?

This page describes how to collect a bid token from the Opera Ads SDK and run the auction on your own server.

If you integrate Opera Ads through a mediation platform (TopOn, TradPlus, AppLovin MAX, AdMob or LevelPlay), Opera Ads serves through that platform's waterfall and you do not need any of the APIs on this page. See the Mediation guides instead.

Overview

Server-to-Server (S2S) Bidding, also known as Server-Side Bidding or Header Bidding, lets your own server collect an Opera Ads bid and run the auction against your other demand sources. The auction is yours — Opera Ads is one of the bidders in it.

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

    • Your own auction server
    • 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: .admob)  // Or .max, .topon, .tradplus, .levelplay, .pangle
            .placementId("s14198264979520")
            .adFormat(adFormat)
            .adSize(.MEDIUM_RECTANGLE)  // Optional: For banner ads
            .build()

        // Create callback
        class Callback: BidTokenCallback {
            let completion: (String?) -> Void
            init(completion: @escaping (String?) -> Void) {
                self.completion = completion
            }
            func onSuccess(bidToken: String) {
                print("Bid token received: \(bidToken.prefix(50))...")
                completion(bidToken)
            }
            func onError(error: OpAdxAdError) {
                print("Bid token request failed: \(error.message)")
                completion(nil)
            }
        }

        // Request bid token
        OpAdxSDK.getBidToken(request, callback: Callback(completion: completion))
    }
}

Objective-C Example:

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

// Implement the OpAdxBidTokenCallback protocol
@interface BidTokenCallbackImpl : NSObject <OpAdxBidTokenCallback>
@property (nonatomic, copy) void (^completion)(NSString * _Nullable);
@end

@implementation BidTokenCallbackImpl

- (void)onSuccessWithBidToken:(NSString *)bidToken {
    NSLog(@"Bid token received: %@...", [bidToken substringToIndex:MIN(50, bidToken.length)]);
    if (self.completion) self.completion(bidToken);
}

- (void)onErrorWithError:(OpAdxAdError *)error {
    NSLog(@"Bid token request failed: %@", error.message);
    if (self.completion) self.completion(nil);
}

@end

@implementation ServerBiddingManager

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

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

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

    if (adFormat == AdFormatBanner) {
        [builder adSize:[OpAdxAdSize MEDIUM_RECTANGLE]];
    }

    OpAdxBidTokenRequest *request = [builder build];

    // Request bid token using protocol callback
    BidTokenCallbackImpl *callback = [[BidTokenCallbackImpl alloc] init];
    callback.completion = completion;
    [OpAdxSDK getBidTokenWithRequest:request callback:callback];
}

@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.loadRtb(
            placementId: placementId,
            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 loadRtbWithPlacementId:placementId bidResponse: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(placementId: "s14198592226752", 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(context: self, placementId: "s14496438551808", bidResponse: bidResponse, listener: loadListener)
}

Bid Token Request Configuration

Ad Mediation Label

BidTokenRequest.Builder requires an adMediation value. It is a reporting label only: the SDK forwards it to the Opera ad server as an attribution tag so Opera can group your S2S traffic in reporting. It does not change the bid, the auction or the ad returned.

This does not enable bidding on a mediation platform

Passing .topon, .max, .admob, .tradplus, .levelplay or .pangle does not make Opera Ads bid inside that platform's auction. Opera Ads is not available as an official bidding network on any mediation platform.

If you serve Opera Ads through a mediation platform, follow the Mediation guides — Opera Ads runs in that platform's waterfall as a custom network and none of the bid token APIs on this page are involved.

Choose the value that matches the stack your own auction result is passed to, or the closest match if you run a fully custom stack.

ValueSwiftObjective-C
AdMob.admob@"admob"
AppLovin MAX.max@"max"
TopOn.topon@"topon"
TradPlus.tradplus@"tradplus"
LevelPlay.levelplay@"levelplay"
Pangle.pangle@"pangle"

Swift:

swift
let request = BidTokenRequest.Builder(adMediation: .admob)
    .placementId("s14198264979520")
    .adFormat(.interstitial)
    .build()

Objective-C:

objective-c
OpAdxBidTokenRequestBuilder *builder = [[OpAdxBidTokenRequestBuilder alloc]
    initWithAdMediation:@"admob"];

Ad Formats

Specify the ad format in your bid token request:

Swift:

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

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

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

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

// App Open
let request = BidTokenRequest.Builder(adMediation: .admob)
    .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(.LARGE_BANNER)

// Leaderboard (728x90)
.adSize(.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: .admob)
            .placementId(placementId)
            .adFormat(adFormat)
            .build()

        class Callback: BidTokenCallback {
            weak var manager: CompleteServerBiddingExample?
            let adFormat: AdFormat
            let placementId: String
            init(manager: CompleteServerBiddingExample, adFormat: AdFormat, placementId: String) {
                self.manager = manager
                self.adFormat = adFormat
                self.placementId = placementId
            }
            func onSuccess(bidToken: String) {
                print("Bid token received")
                manager?.sendToAuctionServer(
                    bidToken: bidToken,
                    adFormat: adFormat,
                    placementId: placementId
                )
            }
            func onError(error: OpAdxAdError) {
                print("Bid token failed: \(error.message)")
            }
        }

        OpAdxSDK.getBidToken(request, callback: Callback(
            manager: self, adFormat: adFormat, placementId: placementId
        ))
    }

    // 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.loadRtb(placementId: placementId, 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: .admob)
    .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.12.0 Last Updated: 2026-08-03 SDK Version: 2.12.0