11.9 — Ad Monetization & SKAdNetwork

Opening scenario

You ship a free utility app — flashlight, calculator, currency converter — pick your noun. After two weeks you have 50,000 users and zero revenue. You drop in AdMob, take a week to integrate, and watch the dashboard: $0.42 the first day. Twenty users hit the ATT prompt and 18 said “Ask App Not to Track.” Your eCPM is $0.30 instead of the $5 you saw in last year’s blog post. You don’t have an ad strategy — you have an ad widget. The actual ad business is a stack: networks, mediation, attribution, ATT consent, SKAdNetwork postbacks, fraud detection, eCPM optimization. This chapter is the field guide.

Context taxonomy

LayerExamplesPurpose
Ad networksAdMob (Google), Meta Audience Network, Unity Ads, ironSource, VungleSources of demand
MediationAdMob Mediation, Max (AppLovin), LevelPlay (ironSource), TradPlusPick the highest-bidding network per impression
MMP (Mobile Measurement Partner)Adjust, Appsflyer, Branch, Kochava, SingularAttribute installs to ad campaigns
ATT (App Tracking Transparency)ATTrackingManager.requestTrackingAuthorization()User permission to use IDFA cross-app
SKAdNetworkApple framework, version 4 in 2026Privacy-preserving install attribution without IDFA
AdServicesApple frameworkApple Search Ads attribution
Ad formatsBanner, Interstitial, Rewarded video, Native, App OpenDifferent RPM tiers
Privacy frameworksApp Privacy Report, Privacy Manifest required 2024+Disclosure of tracking

Concept → Why → How → Code

Concept. Ad monetization is a multi-layer stack: networks bring demand, mediation routes impressions to highest bidders, attribution closes the loop from campaign spend to installs and post-install events, and Apple’s privacy frameworks (ATT, SKAdNetwork, Privacy Manifest) gate everything.

Why. Ad ARPU is small per user ($0.50–$5/yr) but scales to massive numbers (millions of MAU). Apps with hundreds of millions of users — TikTok, Snapchat, ad-supported games — earn most of their revenue from ads. For most indie apps, ads at small scale are pocket change; at 1M+ MAU, ads become a viable business.

Ad network landscape (2026 eCPM benchmarks)

NetworkBest foriOS eCPM (US, post-ATT)Notes
Google AdMobDefault integration, broad demand$3–8 banner, $15–40 rewardedLowest ops burden
Meta Audience NetworkCasual games, social$2–7 banner, $10–30 rewardedRecovered post-ATT through SKAd
Unity AdsGames (Unity-shipped)$5–15 interstitial, $20–50 rewardedStrong rewarded video
ironSource (Unity)Games mediation + own demandsimilarOften part of LevelPlay mediation
AppLovin / MaxMediation default 2026$4–10 interstitial avgMost respected mediation; Max audited
Vungle (Liftoff)Mid-tier video, casino$8–20 videoNiche but strong
TikTok Ads (Pangle)High demand 2026$3–8 banner, $15–40 rewardedFast-growing demand

eCPM: effective revenue per 1,000 impressions. Rewarded video > Interstitial > Native > Banner > App Open in revenue, by roughly that order.

Ad format strategy

Banner          ← always-on, low intrusion, $0.30–$3 eCPM
Interstitial    ← full-screen between game levels, $3–10 eCPM (don't show > 1/min)
Rewarded video  ← user opts in for in-game reward, $10–50 eCPM, BEST format
Native          ← matches app UI, $1–5 eCPM, hard to integrate cleanly
App Open        ← shown on cold launch, $1–4 eCPM, polarizing UX

The dominant 2026 pattern for free apps with revenue: rewarded video + interstitial mediation. Banner-only is a 2018 strategy and earns coffee money.

// AppDelegate.swift or main App init
import AppTrackingTransparency
import AdSupport

func requestTrackingPermission() async {
    guard ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { return }

    // Wait briefly for app to settle (Apple recommends not asking on first launch)
    try? await Task.sleep(nanoseconds: 1_500_000_000)

    let status = await ATTrackingManager.requestTrackingAuthorization()

    switch status {
    case .authorized:
        let idfa = ASIdentifierManager.shared().advertisingIdentifier
        // IDFA available for cross-app tracking
    case .denied, .restricted, .notDetermined:
        // IDFA returns 00000000-0000-0000-0000-000000000000
        break
    @unknown default: break
    }
}

Info.plist requirement:

<key>NSUserTrackingUsageDescription</key>
<string>We use this to show you more relevant ads and improve our app.</string>

ATT opt-in rates in 2026:

  • Top-tier apps with great pre-prompt explanation: 30–45%
  • Average: 20–30%
  • Poor pre-prompts or no explanation: 10–15%

A “pre-prompt” is your own custom UI shown before triggering the system prompt — explaining why and what the user gains by opting in. Big lift on opt-in rate.

SKAdNetwork — privacy-preserving attribution

When ATT is denied (most users), advertisers can’t track installs via IDFA. SKAdNetwork is Apple’s replacement: cryptographically signed postbacks that tell the ad network “an install happened” without revealing the user’s identity.

Flow:
1. User taps ad in App A (publisher)
2. App A calls SKAdNetwork.startImpression(...)
3. User taps ad → App B (advertised app) installs
4. App B calls SKAdNetwork.updatePostbackConversionValue(...) per user activity
5. 24h+ random delay → Apple sends signed postback to App B's ad network
6. Postback includes: ad campaign ID, ad network ID, conversion value (0-63),
   but NOT user ID
// App B (the advertised app) — on first launch
import StoreKit

SKAdNetwork.registerAppForAdNetworkAttribution()  // SKAdNetwork 1.0 legacy
// Or modern:
do {
    try await SKAdNetwork.updatePostbackConversionValue(0)
} catch { /* handle */ }

// As user completes onboarding / purchase, increase conversion value
do {
    try await SKAdNetwork.updatePostbackConversionValue(15,
                                                       coarseValue: .high,
                                                       lockWindow: false)
} catch { /* handle */ }

Conversion value is a 6-bit number (0–63) you encode meaning into. Typical encoding:

  • 0: install only
  • 1–10: completed onboarding
  • 11–30: in-app purchase, dollar bins
  • 31–63: high-value events (subscription, repeat purchase)

Your ad network defines the encoding; you implement it; their dashboard decodes it.

AdServices — Apple Search Ads attribution

import AdServices

if let token = try? AAAttribution.attributionToken() {
    // Send to your server
    let url = URL(string: "https://api-adservices.apple.com/api/v1/")!
    var req = URLRequest(url: url)
    req.httpMethod = "POST"
    req.httpBody = token.data(using: .utf8)
    req.setValue("text/plain", forHTTPHeaderField: "Content-Type")

    let (data, _) = try await URLSession.shared.data(for: req)
    // data contains: campaignId, adgroupId, keywordId, etc.
}

This is the only deterministic install attribution still allowed for Apple Search Ads. Other ad networks use SKAdNetwork.

Privacy Manifest (required since 2024)

Apple requires PrivacyInfo.xcprivacy declaring:

  • Data types collected
  • Tracking domains
  • Required Reason APIs used (e.g., UserDefaults, SystemBootTime)
<dict>
    <key>NSPrivacyTracking</key>
    <true/>
    <key>NSPrivacyTrackingDomains</key>
    <array>
        <string>ads.example-network.com</string>
        <string>track.appsflyer.com</string>
    </array>
    <key>NSPrivacyCollectedDataTypes</key>
    <array>
        <dict>
            <key>NSPrivacyCollectedDataType</key>
            <string>NSPrivacyCollectedDataTypeDeviceID</string>
            <key>NSPrivacyCollectedDataTypeLinked</key>
            <true/>
            <key>NSPrivacyCollectedDataTypeTracking</key>
            <true/>
            <key>NSPrivacyCollectedDataTypePurposes</key>
            <array>
                <string>NSPrivacyCollectedDataTypePurposeAdvertising</string>
            </array>
        </dict>
    </array>
</dict>

Apple rejects builds without correct Privacy Manifests as of 2024.

Mediation example with AdMob

import GoogleMobileAds

// Initialize
@main
struct MyApp: App {
    init() {
        MobileAds.shared.start()
    }
}

// Rewarded video
final class RewardedAdLoader {
    private var rewardedAd: RewardedAd?

    func load() async throws {
        rewardedAd = try await RewardedAd.load(
            with: "ca-app-pub-XXX/YYY",
            request: Request()
        )
    }

    @MainActor
    func show(from vc: UIViewController) async throws -> Bool {
        guard let ad = rewardedAd else { return false }
        return await withCheckedContinuation { cont in
            ad.present(from: vc) {
                let reward = ad.adReward
                cont.resume(returning: true)
                Task { try? await self.load() }   // Pre-load next
            }
        }
    }
}

AdMob’s mediation feature lets you bid out impressions to AppLovin, Meta, Unity, etc. — auctioning each impression in real-time. eCPM uplift of 10–30% typical.

When ads destroy UX (anti-pattern checklist)

❌ Interstitial on every screen transition (Apple Review flags this) ❌ App Open ad shown on every cold launch (frustrating; bad reviews) ❌ Banner that overlaps content (Apple Review rejects) ❌ Misleading “X” button that opens ad instead of closes ❌ Auto-play video with sound on ❌ More than ~3% of session time spent on ads ❌ Ad in onboarding flow (kills retention) ❌ Cannot close ad without watching full 30 seconds

The line: ads should feel like the occasional cost of using a free product, not the product itself. Apps that cross the line get 1-star reviews and tanked retention.

In the wild

  • TikTok earns ~$15B/yr from ads. Almost zero in-app purchases relative to ad revenue.
  • Duolingo’s free tier shows interstitials every ~3 lessons; rewarded video for hearts. ~30% of revenue from ads, 70% from Super.
  • Royal Match (King) — runs almost 100% on interstitials and rewarded video; $2B+ annual revenue.
  • Snapchat — ads, AR filter purchases, Snap+ subscription. Ads dominant.
  • Reddit official app — runs banner + native ads. Eyeballed eCPM higher than expected because of strong audience targeting.

Common misconceptions

  1. “Ad ARPU is high.” Median ad ARPU is $1–3/user/year. You need huge MAU before ads pay rent.
  2. “ATT killed mobile ads.” It killed deterministic cross-app tracking. SKAdNetwork attribution still works at lower fidelity. Ad spend shifted, didn’t vanish.
  3. “Banner ads are the default.” They’re the lowest-revenue format. Rewarded video is 20–100× more lucrative per impression. Default to rewarded video where it fits.
  4. “You can skip ATT prompt if you’re not tracking.” You don’t need to ask permission if you don’t use IDFA. But not asking means SKAdNetwork-only attribution — works but more limited.
  5. “Mediation is too complex for indies.” AdMob Mediation is a couple of config screens. Doubles eCPM. Skip it at your peril.

Seasoned engineer’s take

TIP. Always pre-prompt before showing the ATT system dialog. Explain what the user gets by allowing tracking (more relevant ads, fewer irrelevant ones). Opt-in rates can double.

WARNING. App Store Review polices ad density aggressively. More than ~3 interstitials per minute, App Open + immediate interstitial, or anything that makes the app feel like an ad delivery vehicle gets rejected.

The right way to think about ads: ads are a layer on top of a great free experience, not a substitute for one. The most lucrative ad-funded apps (TikTok, Duolingo, Royal Match) are also the most loved. Bad UX + ads = bad reviews and zero retention; great UX + ads = sustainable business at scale.

Interview corner

Junior“What is ATT?” App Tracking Transparency — Apple’s permission framework requiring user consent before an app can use IDFA for cross-app tracking. Introduced iOS 14.5.

Mid“How do ads still get attributed after ATT?” SKAdNetwork — Apple’s cryptographically-signed postback system that tells ad networks when installs happen without revealing user identity. Conversion value (0–63) encodes app-defined success events.

Senior“Design ad monetization for a free utility app at 5M MAU.” Default rewarded video for premium features; light interstitial after natural pause points (post-result screens). AdMob with Mediation to AppLovin Max, Meta, Unity for bidding. ATT pre-prompt explaining relevance benefit; opt-in target 30%. SKAdNetwork conversion values encoded for in-app conversions. Privacy Manifest declared. Annual eCPM optimization review: rotate mediation waterfall based on actual fill + eCPM data per network.

Red flag“We ask for ATT permission immediately on first launch with no explanation.” Opt-in rate will be <15%. Add a pre-prompt screen explaining the benefit; conversion can double.

Lab preview

The labs focus on subscriptions, which are the dominant 2026 monetization. Ad integration is well-supported by SDK setup wizards from AdMob/Max — not a unique learning surface, but the strategic patterns from this chapter apply directly.


Next: 11.10 — Analytics, RevenueCat & Growth