Skip to main content

Overview

Attribution tells you how users discovered your app. SpendOwl fetches attribution data from Apple Search Ads and enriches it with campaign, ad group, and keyword details.

Automatic Attribution

Attribution is automatically fetched when you call configure(). For most apps, you don’t need to do anything else—the data flows to your dashboard automatically.

Manual Attribution Access

Use the attribution() method to access attribution data in your app:
Task {
    do {
        let attribution = try await SpendOwl.attribution()

        switch attribution.status {
        case .attributed:
            print("Campaign: \(attribution.campaignName ?? "unknown")")
            print("Keyword: \(attribution.keyword ?? "unknown")")
        case .organic:
            print("Organic install")
        case .unknown:
            print("Attribution pending")
        }
    } catch {
        print("Error: \(error)")
    }
}

Attribution Result

The AttributionResult struct contains all available attribution data:
public struct AttributionResult {
    let id: String                  // Unique attribution ID
    let status: AttributionStatus   // .attributed, .organic, or .unknown

    // Campaign data (only when status == .attributed)
    let campaignId: Int?
    let campaignName: String?
    let adGroupId: Int?
    let adGroupName: String?
    let keywordId: Int?
    let keyword: String?

    // Additional data
    let countryOrRegion: String?    // "US", "GB", "JP", etc.
    let clickDate: Date?            // When the ad was clicked
    let conversionType: String?     // "Download" or "Redownload"
    let supplyPlacement: String?    // Ad placement type
}

Attribution Status

StatusDescription
.attributedUser installed after clicking an Apple Search Ads ad. Campaign data is available.
.organicUser found the app organically (not through ads). No campaign data.
.unknownAttribution couldn’t be determined. May be pending or unavailable.

Use Cases

Display Campaign Info

Show users which campaign brought them:
struct WelcomeView: View {
    @State private var campaignName: String?

    var body: some View {
        VStack {
            Text("Welcome!")
            if let campaign = campaignName {
                Text("You found us through: \(campaign)")
                    .font(.caption)
            }
        }
        .task {
            if let attr = try? await SpendOwl.attribution(),
               attr.status == .attributed {
                campaignName = attr.campaignName
            }
        }
    }
}

Analytics Integration

Send attribution to your analytics platform:
func trackAttribution() async {
    guard let attribution = try? await SpendOwl.attribution() else { return }

    Analytics.track("app_install", properties: [
        "source": attribution.status == .attributed ? "paid" : "organic",
        "campaign": attribution.campaignName ?? "none",
        "keyword": attribution.keyword ?? "none",
        "country": attribution.countryOrRegion ?? "unknown"
    ])
}

A/B Testing by Source

Customize experience based on acquisition source:
func getOnboardingFlow() async -> OnboardingFlow {
    guard let attribution = try? await SpendOwl.attribution() else {
        return .standard
    }

    if attribution.status == .attributed,
       attribution.keyword?.contains("premium") == true {
        return .premium
    }

    return .standard
}

Caching

Attribution data is fetched once and cached:
  • First call fetches from Apple and SpendOwl servers
  • Subsequent calls return the cached result
  • Cache persists across app launches
Attribution doesn’t change after install, so caching is safe.

Error Handling

Handle attribution errors gracefully:
do {
    let attribution = try await SpendOwl.attribution()
    // Use attribution
} catch SpendOwlError.notConfigured {
    // SDK not configured - call configure() first
} catch SpendOwlError.attributionUnavailable {
    // Device doesn't support attribution (iOS < 14.3)
} catch SpendOwlError.networkError {
    // Network issue - retry later
} catch {
    // Other error
}

Next Steps