BiddingStack iOS SDK Integration Guide
iOS header bidding SDK built on OpenRTB 2.5. Supports banner, interstitial, rewarded, native, and outstream video ad formats with Google Ad Manager (GAM) integration. View Changelog.
| Component | Minimum Version |
|---|---|
| Xcode | 16.0+ |
| iOS Deployment Target | 15.6+ |
| Swift | 5.0+ |
Drag the frameworks into your Xcode project:
BiddingStackSDK.xcframework: set to Embed & SignOMSDK_Transfon.xcframework: set to Embed & SignBiddingStackGAMAdapter.xcframework: set to Do Not Embed (static framework)Add the Google Mobile Ads SDK via Swift Package Manager or CocoaPods:
// SPM: https://github.com/googleads/swift-package-manager-google-mobile-ads
// CocoaPods: pod 'Google-Mobile-Ads-SDK', '>= 13.0'
For video ads, also add the Google IMA SDK:
// SPM: https://github.com/googleads/swift-package-manager-google-interactive-media-ads-ios
// CocoaPods: pod 'GoogleAds-IMA-iOS-SDK'
Add the following keys to your Info.plist:
Google Ad Application Identifier:
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX</string>
App Transport Security:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Tracking Usage Description (for iOS 14+ ATT):
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
SKAdNetwork Identifiers:
| SKAdNetwork ID | Company |
|---|---|
cstr6suwn9 | |
4fzdc2evr5 | Aarki |
2fnua5tdw4 | AppLovin |
ydx93a7ass | Adikteev |
p78axxw29g | ByteDance (Pangle) |
v72qych5uu | Appier |
c6k4g5qg8m | Beeswax (Freewheel) |
hs6bdukanm | Criteo |
mlmmfzh3r3 | Appreciate |
wzmmz9fp6w | Snap Inc. |
yclnxrl5pm | Jampp |
t38b2kh725 | Lifestreet |
7ug5zh24hu | Liftoff (Vungle) |
9t245vhmpl | Moloco |
44jx6755aq | Persona.ly |
4468km3ulz | Kayzen |
tl55sbb4fm | PubNative (Verve Group) |
2u9pt9hc89 | Remerge |
8s468mfl3y | RTB House |
klf5c3l5u5 | Sift |
ppxm28t8ap | Smadex |
3rd42ekr43 | YouAppi |
| Others | Google (Demand Partners) |
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>22mmun2rn5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2fnua5tdw4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2u9pt9hc89.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qcr597p9d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qy4746246.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3rd42ekr43.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3sh42y64q3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4468km3ulz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>44jx6755aq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>47vhws6wlr.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4dzt52r2t5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>578prtvx9j.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7ug5zh24hu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8c4e2ghe7u.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8s468mfl3y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>97r2b46745.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9t245vhmpl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a2p9lx4jpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c3frkrj4fj.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c6k4g5qg8m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cp8zw746q7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>e5fvkxwrpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f38h382jlk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta9lk7p23.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hs6bdukanm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>k674qkevps.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbd757ywx3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbmxgpxpgc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>klf5c3l5u5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ludvb6z3bs.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mlmmfzh3r3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n38lu8286q.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>p78axxw29g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ppxm28t8ap.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s39g8k73mm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>su67r6k2v3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>t38b2kh725.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>tl55sbb4fm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>uw77j35x4d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v4nxqhlyqp.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v72qych5uu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v9wttpbfk9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vutu7akeur.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wg4vff78zm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wzmmz9fp6w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>y5ghdn5j9k.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>yclnxrl5pm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ydx93a7ass.skadnetwork</string>
</dict>
</array>
Initialize the SDK in AppDelegate.application(_:didFinishLaunchingWithOptions:):
import BiddingStackSDK
BiddingStack.shared.initialize(
accountId: "your_account_id",
enableTestMode: false
) {
print("SDK initialized, config loaded")
}
| Parameter | Type | Default | Description |
|---|---|---|---|
accountId | String | required | Your BiddingStack Account ID |
appBundle | String? | nil | App bundle ID (auto-detected from Info.plist) |
appName | String? | nil | App name (resolved from config if nil) |
appStoreId | String? | nil | App Store ID (resolved from config if nil) |
timeout | Int | 2000 | Bid request timeout in ms |
enableTestMode | Bool | false | Enable debug logging and test mode |
currency | String | "USD" | Currency code for bid requests |
enableCMP | Bool | true | Auto-read IAB CMP strings from UserDefaults |
requestATTPermission | Bool | true | Prompt for App Tracking Transparency permission during initialization |
completion | (() -> Void)? | nil | Called after remote config is loaded |
Note: App bundle, name, store ID, and GAM ad unit paths are resolved automatically from the remote config. You only need to pass the account ID.
// User data
BiddingStack.shared.setUserId("user_123")
BiddingStack.shared.setUserGender("M")
BiddingStack.shared.setUserYob(1990)
// Privacy
BiddingStack.shared.setCoppa(false)
BiddingStack.shared.setGDPR("TCF_consent_string", gdprApplies: true)
BiddingStack.shared.setUSPrivacy("1YNN")
BiddingStack.shared.setGPP("gpp_string", sid: [2])
// App metadata (resolved from config if not set)
BiddingStack.shared.setAppStoreUrl("https://apps.apple.com/app/id123456")
BiddingStack.shared.setAppCategories(["IAB1", "IAB2"])
// Supply chain
BiddingStack.shared.setSchainObject("{...}")
BiddingStack.shared.setExternalUserIds(["partner": "uid_abc"])
BiddingStack.shared.setBuyerUid("buyer_123")
BiddingStack.shared.setUserLatLng(lat: 37.7749, lon: -122.4194)
Pass the web URL of the content the user is currently viewing so it is included in every bid request as app.content.url. This is important for brand safety and yield: it gives bidders page-level context to verify where their ads run, which increases bid rates and CPMs. Set it before loading ads and update it on each screen change; pass nil to clear.
BiddingStack.shared.setContentUrl("https://www.example.com/article/12345")
Available since SDK 26.7.1.
// 1. Create ad unit (gamAdUnitId resolved from config; hardcoded value serves as fallback)
let bannerAdUnit = BiddingStackBannerAdUnit(
configId: "your_config_id",
gamAdUnitId: "your_gam_ad_unit_id", // optional fallback if config API is unavailable
adSizes: [BiddingStackAdSize(width: 300, height: 250)]
)
// 2. Create banner view
let bannerView = BiddingStackBannerView(adUnit: bannerAdUnit)
// 3. Set GAM provider
let provider = GAMBannerProvider(
adUnitID: bannerAdUnit.resolvedGamAdUnitId,
rootViewController: self,
adSizes: bannerAdUnit.adSizes
)
bannerView.setAdServerProvider(provider)
// 4. Set callbacks
bannerView.onAdLoaded = { print("Banner loaded") }
bannerView.onAdFailedToLoad = { error in print("Failed: \(error)") }
// 5. Add to view and load
view.addSubview(bannerView)
bannerView.loadAd()
| Parameter | Type | Default | Description |
|---|---|---|---|
configId | String | required | Placement config ID |
gamAdUnitId | String | "" | GAM ad unit path (resolved from config if empty) |
adSizes | [BiddingStackAdSize] | required | Ad sizes |
autoRefreshEnabled | Bool | false | Auto-refresh ads |
lazyLoadEnabled | Bool | false | Lazy load when visible |
isAutoLoadAd | Bool | true | Auto-load on creation |
formats | [BiddingStackFormat] | [.banner] | .banner, .video |
refreshInterval | TimeInterval | 30 | Refresh interval in seconds |
let videoAdUnit = BiddingStackBannerAdUnit(
configId: "your_video_config_id",
adSizes: [BiddingStackAdSize(width: 300, height: 250)],
formats: [.video]
)
let videoView = BiddingStackBannerView(adUnit: videoAdUnit)
videoView.videoRenderer = BiddingStackIMAVideoRenderer()
videoView.onAdLoaded = { print("Video loaded") }
videoView.onAdPlaybackCompleted = { print("Video complete") }
videoView.loadAd()
let interstitialAdUnit = BiddingStackInterstitialAdUnit(
configId: "your_config_id",
size: BiddingStackAdSize(width: 320, height: 480)
)
BiddingStack.shared.loadInterstitialAd(adUnit: interstitialAdUnit) { ad, error in
if let ad = ad {
ad.fullScreenContentDelegate = self
ad.present(from: self)
}
}
App open ads show at natural app-entry points, such as launch or returning from the background. Preload the ad with BiddingStackInterstitialBanner, then show it when the app becomes active.
class AppOpenAdManager: NSObject, BiddingStackInterstitialDelegate {
static let shared = AppOpenAdManager()
private var interstitial: BiddingStackInterstitialBanner?
private var isLoading = false
func loadAd() {
if isLoading || interstitial?.isAdReady() == true { return }
isLoading = true
// Google interstitial size: 320x480 phone, 768x1024 tablet
let interstitialSize = UIDevice.current.userInterfaceIdiom == .pad
? BiddingStackAdSize(width: 768, height: 1024)
: BiddingStackAdSize(width: 320, height: 480)
let adUnit = BiddingStackBannerAdUnit(
configId: "your_config_id",
gamAdUnitId: "your_gam_ad_unit_id",
adSizes: [interstitialSize, BiddingStackAdSize(width: 300, height: 250)],
autoRefreshEnabled: false
)
let banner = BiddingStackInterstitialBanner(adUnit: adUnit)
banner.delegate = self
if let root = UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.rootViewController {
banner.setAdServerProvider(GAMBannerProvider(
adUnitID: adUnit.resolvedGamAdUnitId,
rootViewController: root,
adSizes: adUnit.adSizes
))
}
interstitial = banner
banner.loadAd()
}
func showAdIfAvailable(from viewController: UIViewController) {
guard let banner = interstitial, banner.isAdReady() else {
loadAd()
return
}
banner.show(from: viewController)
}
func interstitialDidReceiveAd() { isLoading = false }
func interstitialDidFailToReceiveAd(error: Error) {
isLoading = false
interstitial = nil
}
func interstitialWillPresent() {}
func interstitialDidDismiss() {
interstitial = nil
loadAd() // Preload the next one
}
}
Show it when the app comes to the foreground, e.g. in your scene delegate:
func sceneDidBecomeActive(_ scene: UIScene) {
if let root = (scene as? UIWindowScene)?.keyWindow?.rootViewController {
AppOpenAdManager.shared.showAdIfAvailable(from: root)
}
}
let rewardedAdUnit = BiddingStackRewardedAdUnit(
configId: "your_config_id",
size: BiddingStackAdSize(width: 320, height: 480)
)
BiddingStack.shared.loadRewardedAd(adUnit: rewardedAdUnit) { ad, error in
if let ad = ad {
ad.present(from: self) {
print("User earned reward!")
}
}
}
let nativeAdUnit = BiddingStackNativeAdUnit(
configId: "your_native_config_id"
)
BiddingStack.shared.loadNativeAd(adUnit: nativeAdUnit) { ad, error in
guard let ad = ad else { return }
print(ad.title)
print(ad.body)
// Register view for impression/click tracking
ad.registerView(
forInteraction: adView,
withViewController: self,
clickableViews: [adView.ctaButton]
)
}
| Property | Type | Description |
|---|---|---|
title | String? | Ad headline |
body | String? | Ad body text |
callToAction | String? | CTA button text |
sponsoredBy | String? | Sponsor/advertiser name |
iconURL | URL? | Icon image URL |
mainImageURL | URL? | Main image URL |
clickURL | URL? | Click-through URL |
Preload a bid before displaying. Cached bid is valid for 30 seconds.
BiddingStack.shared.preloadBid(for: bannerAdUnit)
// Later, loadAd() uses the cached bid automatically
BiddingStack.shared.addTargetingKV(key: "interest", value: "tech")
BiddingStack.shared.addTargetingKV(key: "interest", values: ["tech", "gaming"])
BiddingStack.shared.removeTargetingKey("interest")
BiddingStack.shared.clearTargetingKV()
// Ad unit-level targeting
bannerAdUnit.addContextKeyword(key: "section", value: "homepage")
let deal = BiddingStackDeal(id: "deal_123", bidfloor: 5.0, bidfloorcur: "USD")
bannerAdUnit.setDeals([deal])
For custom ad server integrations:
BiddingStack.shared.fetchDemand(for: adUnit) { result in
switch result {
case .success(let targeting):
print(targeting) // [String: String] bid keywords
case .failure(let error):
print(error)
}
}
| Class | Description |
|---|---|
GAMBannerProvider | Ad server provider for GAM banners |
BiddingStackGAMUtils | Apply targeting keys to AdManagerRequest |
BiddingStackGAMLoader | Load GAM ads with BiddingStack demand |
BiddingStackIMAVideoRenderer | Video renderer using Google IMA SDK |
// Banner
BiddingStackGAMLoader.loadBanner(
biddingStackAdUnit: bannerAdUnit,
rootViewController: self
) { result in ... }
// Interstitial
BiddingStackGAMLoader.loadInterstitial(
biddingStackAdUnit: interstitialAdUnit
) { result in ... }
The SDK automatically reads IAB consent strings from UserDefaults:
IABTCF_TCString and IABTCF_gdprAppliesIABUSPrivacy_StringIABGPP_HDR_GppString and IABGPP_SIDAppTrackingTransparencyBiddingStack.shared.initialize(..., enableTestMode: true)
Log prefixes: BS-Info (lifecycle), BS-Debug (bid data), BS-Error (issues).
All SDK logs are also emitted via Apple's unified logging under the subsystem com.biddingstack.sdk, so they can be inspected on any device — including production/TestFlight builds — without code changes:
subsystem:com.biddingstack.sdkverbose category carries full bid request/response JSONOr from the terminal:
log stream --level debug --predicate 'subsystem == "com.biddingstack.sdk"'
When running from Xcode, add the launch argument -BSDebugLogging YES to also mirror all logs (including verbose) to the console.