Apple Search Ads Setup & Implementation | OpsBlu Docs

Apple Search Ads Setup & Implementation

How to implement Apple Search Ads with Apple Ads Attribution API and SKAdNetwork. Covers AdServices framework, postback configuration, custom product.

Apple Search Ads is the only advertising platform that places your app at the top of App Store search results. Unlike web-based ad platforms where you drop a pixel on a page, Apple Search Ads attribution is deeply tied to Apple's privacy frameworks -- AdServices, SKAdNetwork, and App Tracking Transparency (ATT). A misconfigured implementation does not just lose data; it means your attribution tokens never generate, your SKAdNetwork postbacks never register, and your campaign optimization runs on zero signal.

Why Proper Implementation Matters

Attribution in Apple's Privacy-First World

Apple Search Ads operates under stricter privacy constraints than any web ad platform:

  • No IDFA by default: Since iOS 14.5, users must opt in via ATT. Typical opt-in rates are 15-30%.
  • AdServices framework replaces the deprecated iAd framework and provides deterministic attribution without requiring user consent
  • SKAdNetwork provides privacy-preserving install attribution with delayed, aggregated postbacks
  • No real-time conversion data: SKAdNetwork postbacks arrive 24-48 hours after install with limited conversion value bits

If you rely solely on ATT consent for attribution, you will lose measurement on 70-85% of installs. A proper implementation layers AdServices attribution (deterministic, no consent needed) with SKAdNetwork (privacy-preserving aggregated data) to maximize coverage.

Campaign Optimization Consequences

Without accurate attribution:

  • Search Match campaigns cannot learn which queries drive installs
  • Cost-per-acquisition (CPA) goals have no conversion signal to optimize against
  • Keyword bid adjustments rely on incomplete data, leading to overbidding on poor keywords
  • Creative Sets and Custom Product Pages cannot be evaluated for performance
  • Audience refinement (age, gender, location) lacks conversion feedback

Revenue Attribution

For subscription apps, the real value of an install happens days or weeks later. Apple Search Ads supports postback conversion values that encode post-install behavior (trial start, subscription purchase, retention milestones). Getting the conversion value schema right determines whether you can measure true ROAS or only install volume.

Pre-Implementation Planning

Access and Permissions

Apple Search Ads Account:

  1. Sign in at searchads.apple.com with your Apple ID
  2. Request Account Admin or Campaign Manager role
  3. For agencies: Accept invitation from the app owner's account
  4. Verify your Apple Developer Program membership is active

Required Access:

Platform Role Needed Purpose
Apple Search Ads Campaign Manager+ Create campaigns, view attribution data
App Store Connect Admin or Developer Access app metadata, configure custom product pages
Xcode Project Developer Add AdServices framework, configure SKAdNetwork
Apple Developer Portal Account Holder/Admin Register SKAdNetwork IDs, manage entitlements

Mobile Measurement Partner (MMP) Decision:

  • If using an MMP (AppsFlyer, Adjust, Branch, Singular, Kochava): The MMP SDK handles AdServices token retrieval and SKAdNetwork postback registration
  • If building in-house: You need to implement AdServices token retrieval and server-side attribution API calls yourself
  • Recommendation: Use an MMP unless you have a dedicated mobile measurement engineering team

Conversion Value Schema Design

SKAdNetwork provides 6 bits (values 0-63) to encode post-install conversion information. Design your schema before writing any code:

Subscription App Example:

Value Range Meaning Timeframe
0 Install only, no engagement Day 0
1-10 App opened X times Days 0-3
11-20 Completed onboarding Days 0-3
21-30 Started free trial Days 0-7
31-40 Trial active, high engagement Days 3-7
41-50 Converted to paid subscription Days 0-14
51-60 Tier 2+ subscription or annual plan Days 0-14
61-63 Reserved for future use --

E-commerce App Example:

Value Range Meaning
0 Install, no activity
1-15 Browsed X product categories
16-30 Added items to cart (value bucket)
31-45 Completed first purchase (revenue bucket)
46-63 Repeat purchaser (LTV bucket)

Design the schema to answer: "What is the highest-value action this user took within the conversion window?"

Implementation Walkthrough

Step 1: Add AdServices Framework to Your Xcode Project

AdServices provides deterministic attribution for Apple Search Ads without requiring ATT consent.

import AdServices

class AttributionManager {

    /// Retrieves the Apple Search Ads attribution token on first launch.
    /// Call this in application(_:didFinishLaunchingWithOptions:) or on first app open.
    func fetchAttributionToken() {
        // AdServices is available on iOS 14.3+
        if #available(iOS 14.3, *) {
            do {
                let token = try AAAttribution.attributionToken()
                // Send this token to your server for attribution lookup
                sendTokenToServer(token: token)
            } catch {
                // Token generation failed -- user may not have come from an ad
                print("AdServices attribution token error: \(error.localizedDescription)")
            }
        }
    }

    private func sendTokenToServer(token: String) {
        guard let url = URL(string: "https://your-server.com/api/attribution") else { return }

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

        let body: [String: Any] = [
            "attribution_token": token,
            "app_bundle_id": Bundle.main.bundleIdentifier ?? "",
            "timestamp": ISO8601DateFormatter().string(from: Date())
        ]

        request.httpBody = try? JSONSerialization.data(withJSONObject: body)

        URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                print("Failed to send attribution token: \(error)")
            }
        }.resume()
    }
}

Step 2: Server-Side Attribution API Call

Your server receives the token from the app and calls Apple's Attribution API to get campaign details.

import requests

APPLE_ATTRIBUTION_URL = "https://api-adservices.apple.com/api/v1/"

def get_attribution(token: str) -> dict:
    """
    Exchange an AdServices token for attribution data.
    Returns campaign name, ad group, keyword, and other details.
    """
    headers = {
        "Content-Type": "text/plain"
    }

    response = requests.post(
        APPLE_ATTRIBUTION_URL,
        data=token,
        headers=headers
    )

    if response.status_code == 200:
        data = response.json()
        # data contains:
        # - attribution: true/false (was this an Apple Search Ads install?)
        # - campaignId, adGroupId, keywordId
        # - campaignName, adGroupName, keyword
        # - clickDate, conversionType
        return data
    elif response.status_code == 404:
        # Not an Apple Search Ads install
        return {"attribution": False}
    else:
        raise Exception(f"Attribution API error: {response.status_code}")

Important timing notes:

  • Call the Attribution API within 24 hours of token generation for best results
  • The API may return a 404 if the install was not from Apple Search Ads
  • Tokens are single-use; do not retry with the same token after a successful call

Step 3: Configure SKAdNetwork

SKAdNetwork provides aggregated, privacy-preserving attribution data even when users have not consented to tracking.

Add SKAdNetwork IDs to Info.plist:

<!-- Info.plist -->
<key>SKAdNetworkItems</key>
<array>
    <!-- Apple Search Ads Network ID -->
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>YDX93A7ASS.skadnetwork</string>
    </dict>
    <!-- Add MMP network IDs if using a measurement partner -->
    <!-- AppsFlyer -->
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>PBHKH8B9YZ.skadnetwork</string>
    </dict>
</array>

Register Conversion Values:

import StoreKit

class ConversionValueManager {

    /// Update the SKAdNetwork conversion value based on user behavior.
    /// Call this whenever the user completes a valuable action.
    func updateConversionValue(for event: AppEvent) {
        let newValue = calculateConversionValue(event: event)

        if #available(iOS 16.1, *) {
            // SKAdNetwork 4.0 -- supports coarse and fine conversion values
            SKAdNetwork.updatePostbackConversionValue(newValue, coarseValue: .high) { error in
                if let error = error {
                    print("SKAdNetwork update error: \(error)")
                }
            }
        } else if #available(iOS 15.4, *) {
            SKAdNetwork.updatePostbackConversionValue(newValue) { error in
                if let error = error {
                    print("SKAdNetwork update error: \(error)")
                }
            }
        } else {
            // iOS 14.5 - 15.3
            SKAdNetwork.registerAppForAdNetworkAttribution()
            SKAdNetwork.updateConversionValue(newValue)
        }
    }

    private func calculateConversionValue(event: AppEvent) -> Int {
        // Map your app events to conversion value schema
        switch event {
        case .appOpen:
            return max(currentValue, 1)
        case .onboardingComplete:
            return max(currentValue, 15)
        case .trialStarted:
            return max(currentValue, 25)
        case .subscriptionPurchased(let tier):
            return max(currentValue, tier == .annual ? 55 : 45)
        default:
            return currentValue
        }
    }
}

Key rules for conversion values:

  • Values can only increase, never decrease (SKAdNetwork ignores lower values)
  • The conversion window is 24 hours by default; each update resets the timer (up to 63 days with SKAdNetwork 4.0)
  • Postbacks arrive 24-48 hours after the conversion window closes
  • Design values so higher numbers always mean higher user value

While not required for AdServices or SKAdNetwork, ATT consent enables richer attribution data.

import AppTrackingTransparency

func requestTrackingPermission() {
    // Request after onboarding, not immediately on first launch
    ATTrackingManager.requestTrackingAuthorization { status in
        switch status {
        case .authorized:
            // Full attribution data available
            // If using MMP, notify it of consent
            print("Tracking authorized")
        case .denied, .restricted:
            // AdServices + SKAdNetwork still work
            print("Tracking denied -- using privacy-preserving attribution")
        case .notDetermined:
            print("Tracking status not yet determined")
        @unknown default:
            break
        }
    }
}

Best practices for ATT prompt:

  • Show a pre-prompt screen explaining why tracking helps (better ads, free app)
  • Request permission after the user has experienced app value, not on first launch
  • Never gate app functionality behind tracking consent
  • Customize the NSUserTrackingUsageDescription string in Info.plist

Step 5: Configure Custom Product Pages (CPPs)

Custom Product Pages let you create up to 35 variants of your App Store listing, each with different screenshots, app previews, and promotional text. Link these to specific ad groups for higher relevance.

  1. In App Store Connect, navigate to your app > Custom Product Pages
  2. Create a new CPP with screenshots and copy matching your ad group's theme
  3. Submit for review (CPPs require Apple approval)
  4. In Apple Search Ads, navigate to Ad Group > Ads
  5. Create a new ad and select the approved CPP
  6. The CPP URL format is: https://apps.apple.com/app/id123456789?ppid=CPP_ID

Verification and QA

AdServices Token Validation

On-Device Testing:

  1. Install a debug build on a physical device (simulators do not generate real tokens)
  2. Search for your app in the App Store and tap the ad (if running test campaigns)
  3. Install and launch the app
  4. Check your server logs for the attribution token submission
  5. Verify the Attribution API response contains "attribution": true with campaign details

Common Issues:

  • Token generation throws error: Ensure AdServices.framework is linked in Xcode Build Phases
  • Attribution API returns 404: The install was organic, not from an ad
  • Empty campaign data: The token may have expired (24-hour window)

SKAdNetwork Postback Validation

Testing SKAdNetwork is inherently difficult because postbacks are delayed and arrive at the ad network's registered URL, not your server.

What you can verify:

  1. Confirm SKAdNetworkItems in Info.plist includes Apple's network ID
  2. Use print statements or analytics events to verify updatePostbackConversionValue is called
  3. In Apple Search Ads, check the "Install (SK)" column in campaign reports (appears 24-48 hours after install)
  4. If using an MMP, check the MMP dashboard for SKAdNetwork install and conversion value data

Campaign Dashboard Verification

In Apple Search Ads at searchads.apple.com:

  1. Navigate to Campaigns > select a campaign > Ad Groups
  2. Check the "Installs" column for attributed installs
  3. Verify "CPA" (cost per acquisition) is calculating correctly
  4. Check "Conversion Rate" (taps to installs) is within expected range (typically 40-60% for search ads)
  5. Under Attribution, verify both AdServices and SKAdNetwork data streams are reporting

Debug Tools

Tool Purpose
Xcode Console View AdServices token generation and SKAdNetwork update calls
Charles Proxy / Proxyman Intercept attribution token POST to your server
Apple Search Ads Dashboard View attributed installs, CPA, and conversion rates
MMP Dashboard (if applicable) Unified view of AdServices + SKAdNetwork attribution
TestFlight Test attribution flow with beta builds (limited -- real attribution requires App Store installs)

Deployment Artifacts

  • AdServices integration code: Token retrieval in app, server-side API endpoint
  • SKAdNetwork configuration: Info.plist network IDs, conversion value schema documentation
  • Conversion value mapping: Spreadsheet mapping app events to SKAdNetwork values 0-63
  • ATT prompt implementation: Pre-prompt screen copy, NSUserTrackingUsageDescription string
  • Custom Product Pages: CPP IDs, associated ad groups, and screenshot variants
  • MMP SDK configuration (if applicable): SDK initialization code, server-to-server callback URLs
  • Campaign structure: Campaign > Ad Group > Keyword hierarchy with max CPA targets

Linked Runbooks

Change Log and Owners

  • Document who manages the Apple Search Ads account and who has App Store Connect access
  • Track conversion value schema changes with version numbers and effective dates
  • Record MMP configuration changes and SDK version updates
  • Maintain a log of Custom Product Page approvals and ad group associations
  • Review quarterly: attribution data completeness, conversion value distribution, CPA trends