In-app environment - Technical Requirements

Welcome to the integration documentation for In-App Bidding mode with Pubstack. This integration relies on Prebid Mobile SDK (v3+), in GAM Bidding-only mode.


✅ Prerequisites

  • Native iOS (Swift) or Android (Kotlin) application (WebView not supported by Prebid Mobile SDK - available with a standard web integration)

  • SDKs to integrate:

    • Prebid Mobile SDK v3+

    • Google Mobile Ads SDK (GMA)

    • Consent Management Platform (CMP) compatible with TCF v2

    • iOS only: ATT (App Tracking Transparency)


1. 🧱 Prebid SDK Initialization

The initialization of the Prebid SDK establishes the connection with Prebid Server – in our case, the server managed or instrumented by Pubstack. This includes configuring the endpoint URL and client identifiers.

Pubstack Identifiers: Pubstack provides an Account ID (publisher ID on Prebid Server) per application (and per OS) and uses config IDs (ad unit configuration IDs) for inventory.

  • The Account ID is used during SDK initialization.

  • Each ad placement will have its own config ID (see AdUnits section).

Android (Kotlin)

// Kotlin (Android) – Prebid Configuration
PrebidMobile.setPrebidServerAccountId("PUBSTACK_ACCOUNT_ID")
PrebidMobile.initializeSdk(
    context,
    "https://prebid-server.pbstck.com/openrtb2/auction",
) { status ->
    if (status == PrebidMobile.InitializationStatus.Succeeded) {
        Log.d("Prebid", "SDK initialized ✅")
    } else {
        Log.e("Prebid", "SDK init failed: $status")
    }
}

iOS (Swift)

import PrebidMobile

// Prebid configuration for iOS
Prebid.shared.prebidServerAccountId = "PUBSTACK_ACCOUNT_ID"
Prebid.shared.prebidServerHost = Host.Custom
Prebid.shared.customPrebidServerUrl = "https://prebid-server.pbstck.com/openrtb2/auction"

PrebidMobile.initializeSDK { status in
    switch status {
    case .succeeded:
        print("Prebid SDK initialized ✅")
    case .failed:
        print("Prebid SDK init failed ❌")
    @unknown default:
        print("Prebid SDK init status unknown ⚠️")
    }
}

In these examples, Prebid is initialized with:

  • Account ID provided by Pubstack (available in Media & stacks > Select your app > Connect to Pubstack).

  • Prebid Server URL: Pubstack endpoint.

Then Prebid Mobile will:

  1. Check Prebid Server health (internal /status call) to ensure it’s reachable.

  2. Prepare its internal components (local cache, etc.).

  3. Use these parameters for all subsequent bid requests.

⚠️ Important: Prebid.initializeSDK must be called only once (typically at app launch, after the CMP). The SDK must be initialized before creating AdUnit objects or calling fetchDemand().


2. 🛡️ CMP and ATT

CMP

The CMP usually provides standardized keys in the app’s local storage, which Prebid SDK can automatically read:

  • IABTCF_gdprApplies → whether the user is subject to GDPR (1 or 0).

  • IABTCF_TCString → encoded TCF v2 consent string summarizing user choices.

  • IABTCF_PurposeConsents → binary string indicating consent or not for each purpose (e.g. the first bit = Purpose 1: access device info).

Prebid SDK reads these values at initialization and on each bid request, without modifying or validating them.

👉 If the CMP stores them in the IAB format, no need to pass them explicitly into Prebid – the SDK will use them automatically.

SDK behavior depending on GDPR consent:

  • Non-GDPR (gdprApplies=false): SDK sends advertising ID (IDFA/AAID) by default.

  • GDPR, no consent for Purpose 1: SDK does not send IDFA/AAID; bid requests are anonymous.

  • GDPR, consent given for Purpose 1: SDK sends IDFA/AAID normally.

  • Invalid or undefined consent: SDK defaults to not sending any ID unless gdprApplies is explicitly false.

✅ To comply with GDPR:

  1. Initialize the CMP at app launch, before requesting ads.

  2. Ensure the TC String is stored locally (NSUserDefaults / SharedPreferences) before initializing Prebid or calling fetchDemand().

Once consent is obtained (or updated during session), Prebid automatically uses the latest value for the next bid request. If consent changes, trigger an update (e.g. re-run fetchDemand or recreate the AdUnit) to reflect new privacy parameters.


ATT (iOS only)

On iOS 14+, AppTrackingTransparency (ATT) requires explicit user permission to access IDFA. This directly impacts Prebid: if the user denies tracking, IDFA won’t be available or sent to partners.

Scenarios:

  • Authorized: App has ATT permission → Prebid SDK retrieves IDFA via AdSupport and includes it in bid requests (unless blocked by GDPR).

  • Denied/Not requested: No IDFA collection possible → Prebid still sends bid request, but without IDFA. Bidders must rely on other signals (IP, context, alternative IDs).

Integration required: Developer must trigger the ATT authorization request via ATTrackingManager.requestTrackingAuthorization at the right moment (often at app launch or before personalized ads). Apple recommends explaining ATT usage to the user before showing the popup.

ATTrackingManager.requestTrackingAuthorization { status in
    // Continue loading ads after response
}

⚠️ Don’t forget to add NSUserTrackingUsageDescription in Info.plist, otherwise the ATT popup won’t show.

<key>NSUserTrackingUsageDescription</key>
<string>We use your IDFA to personalize advertising.</string>

3. 📦 Declaring AdUnits

A Pubstack ad unit represents an ad placement in the app, linked to a bidding configuration on Prebid Server.

Currently, Pubstack supports Banner and Interstitial formats.


Each Banner AdUnit is defined by:

  • A configId (identifier of the placement configured in Pubstack/Prebid Server).

  • A size (largest standard size the placement can accept).

💡 This size will be the only one injected in the bid request.

Naming rules – pbAdSlot (pubstack_adunit_name):

It is important to clearly name and identify placements. Prebid provides the pbAdSlot field (placement name) to pass the ad unit name to the server. Pubstack uses it to track the adunit_name.

  • Sent in imp.ext.data.pbadslot of the bid request.

  • Stored by Pubstack.

  • Prebid 3.0 also supports GPID (Global Publisher ID) for standardized placement IDs (often GAM code) → adUnit.setGpid("...") on Android / adUnit.gpid = "..." on iOS.

iOS Example

let adUnit = BannerAdUnit(configId: "pubstack_adunit_name", size: CGSize(width:300, height:250))
adUnit.pbAdSlot = "pubstack_adunit_name"
adUnit.gpid = "/\(GAMNetworkCode)/\(AdUnitPath)"   // e.g. "/123456/homepage/top_banner"

Android Example

val adUnit = BannerAdUnit("pubstack_adunit_name", 320, 50)
adUnit.pbAdSlot = "pubstack_adunit_name"
adUnit.setGpid("/<NETWORK_CODE>/<AD_UNIT_PATH>")  // e.g. "/123456/homepage/top_banner"

Interstitial

Each Interstitial AdUnit is defined by:

  • configId: ad unit name in Pubstack.

  • minWidthPerc & minHeightPerc: % of screen width/height the ad must occupy to qualify as an interstitial.

Example:

  • minWidthPerc=80 → must cover ≥80% of screen width.

  • minHeightPerc=60 → must cover ≥60% of screen height.

As with banners, use clear naming rules:

  • pbAdSlot (pubstack_adunit_name) → sent in imp.ext.data.pbadslot and stored by Pubstack.

  • GPID (optional but recommended) → standardized GAM ad unit ID, sent in imp.gpid.

iOS Example

let interstitialAdUnit = InterstitialAdUnit(configId: "pubstack_adunit_name", minWidthPerc: 80, minHeightPerc: 60)
interstitialAdUnit.pbAdSlot = "pubstack_adunit_name"
interstitialAdUnit.gpid = "/\(GAMNetworkCode)/\(AdUnitPath)"   // e.g. "/123456/app/interstitial"

Android Example

val interstitialAdUnit = InterstitialAdUnit("pubstack_adunit_name", 80, 60)
interstitialAdUnit.pbAdSlot = "pubstack_adunit_name"
interstitialAdUnit.setGpid("/<NETWORK_CODE>/<AD_UNIT_PATH>")  // e.g. "/123456/app/interstitial"

4. 🎯 Prebid Fetch & GAM Ad Load

Once the Prebid AdUnit is configured, the flow to display an ad via GAM is:

  1. Prebid auction call → Trigger fetchDemand() on the Prebid AdUnit, passing the GAM ad request object.

  2. GAM Ad Request → GAM SDK sends the request with the ad unit ID + Prebid-enriched targeting.

  3. Creative rendering → If a Prebid line item matches, GAM serves a universal creative (Prebid Universal Creative or cache-based) which retrieves and renders the actual winning ad.

🤖 Android Example (Kotlin, GMA SDK 23.6.0+)

package io.pubstack.prebid_sdk.component

import android.content.Context
import android.util.Log
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.gms.ads.AdSize
import com.google.android.gms.ads.admanager.AdManagerAdRequest
import com.google.android.gms.ads.admanager.AdManagerAdView
import org.prebid.mobile.BannerAdUnit

@Composable
fun PrebidBannerAd(
    adUnitId: String,          // GAM Ad Unit ID (ex: "/123456/home/top_banner")
    configId: String,          // Pubstack adunit name
    width: Int,                // Banner width (e.g., 320)
    height: Int,               // Banner height (e.g., 50)
    context: Context = LocalContext.current
) {
    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .height(height.dp),
        factory = {
            // 1) Create GAM banner view
            val adView = AdManagerAdView(context).apply {
                adUnitId = adUnitId
                setAdSizes(AdSize(width, height))
            }

            // 2) Create Prebid banner ad unit
            val adUnit = BannerAdUnit(configId, width, height).apply {
                pbAdSlot = configId
                gpid = adUnitId
            }

            // 3) Create GAM request
            val gamRequest = AdManagerAdRequest.Builder().build()

            // 4) Enrich GAM request with Prebid demand
            adUnit.fetchDemand(gamRequest) { resultCode ->
                Log.d("PrebidBannerAd", "Prebid fetch result: $resultCode")
                // 5) Load GAM banner with SAME enriched request
                adView.loadAd(gamRequest)
            }

            adView
        }
    )
}

Interstitial

import android.app.Activity
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.admanager.AdManagerAdRequest
import com.google.android.gms.ads.admanager.AdManagerInterstitialAd
import com.google.android.gms.ads.admanager.AdManagerInterstitialAdLoadCallback

fun loadInterstitial(activity: Activity, adUnitId: String) {
    // 1) Create a GAM request
    val gamRequest = AdManagerAdRequest.Builder().build()

    // 2) Enrich with Prebid targeting
    interstitialAdUnit.fetchDemand(gamRequest) { _ ->
        // 3) Load the GAM interstitial with the SAME enriched request
        AdManagerInterstitialAd.load(
            activity,
            adUnitId,
            gamRequest,
            object : AdManagerInterstitialAdLoadCallback() {
                override fun onAdLoaded(ad: AdManagerInterstitialAd) {
                    // 4) Present at the right UX moment
                    ad.show(activity)
                }
                override fun onAdFailedToLoad(error: LoadAdError) {
                    // Handle failure (retry/backoff/metrics)
                }
            }
        )
    }
}

🍏 iOS Example (Swift, GMA SDK 11.9.0+)

import UIKit
import GoogleMobileAds
import PrebidMobile

final class BannerController {

    private var gamBanner: GAMBannerView?
    private let bannerAdUnit: BannerAdUnit
    private let gamAdUnitId: String

    init(configId: String, gamAdUnitId: String) {
        self.bannerAdUnit = BannerAdUnit(configId: configId, size: CGSize(width: 320, height: 50))
        self.bannerAdUnit.pbAdSlot = configId
        self.bannerAdUnit.gpid = gamAdUnitId
        self.gamAdUnitId = gamAdUnitId
    }

    func load(in viewController: UIViewController, container: UIView) {
        // 1️) Create the GAM banner view
        let gamBanner = GAMBannerView(adSize: GADAdSizeBanner)
        gamBanner.adUnitID = gamAdUnitId
        gamBanner.rootViewController = viewController
        container.addSubview(gamBanner)
        gamBanner.center = container.center

        // 2️) Create a GAM request
        let request = GAMRequest()

        // 3️) Enrich with Prebid demand
        bannerAdUnit.fetchDemand(adObject: request) { result in
            print("Prebid fetchDemand result: \(result.name())")
            // 4️) Load the GAM banner ad
            gamBanner.load(request)
        }

        self.gamBanner = gamBanner
    }
}

Interstitial

import GoogleMobileAds
import PrebidMobile

final class InterstitialController {
    private var gamInterstitial: GAMInterstitialAd?
    private let interstitialAdUnit: PrebidMobile.InterstitialAdUnit
    private let gamAdUnitId: String

    init(configId: String, gamAdUnitId: String) {
        self.interstitialAdUnit = PrebidMobile.InterstitialAdUnit(
            configId: configId,
            minWidthPerc: 80,
            minHeightPerc: 60
        )
        self.gamAdUnitId = gamAdUnitId
    }

    func load(rootViewController: UIViewController) {
        // 1) Create a GAM request
        let request = GAMRequest()

        // 2) Enrich with Prebid targeting
        interstitialAdUnit.fetchDemand(adObject: request) { _ in
            // 3) Load the GAM interstitial with the SAME enriched request
            GAMInterstitialAd.load(withAdManagerAdUnitID: self.gamAdUnitId,
                                   request: request) { [weak self] ad, error in
                guard let self = self, let ad = ad, error == nil else {
                    // Handle failure (retry/backoff/metrics)
                    return
                }
                self.gamInterstitial = ad

                // 4) Present at the right UX moment (not necessarily immediately)
                ad.present(fromRootViewController: rootViewController)
            }
        }
    }
}

🔎 How it works

  • fetchDemand(): Sends an OpenRTB request to Prebid Server (with size, configId, pbAdSlot/gpid).

  • Prebid Server: Runs the auction across connected SSPs/DSPs and returns the winning bid.

  • Prebid SDK: Adds targeting key-values into the GAM request (bid_bidder, bid_cpm, hb_cache_id, etc.).

  • GAM: Uses these targeting keys to match against Prebid line items. If a Prebid line item wins, GAM serves the universal creative which then fetches and renders the actual winning ad.


5. 🧪 Test & Debug

  • Check Prebid logs for bid responses.

  • Ensure GAM request contains targeting keys (bid_bidder, bid_cpm, etc.).

  • Use Google Ad Inspector for local testing.

Last updated