Conversion Event Types
Install Attribution
Track app installations from Search Ads:
import AdServices
func trackInstallAttribution() {
guard let token = try? AAAttribution.attributionToken() else {
print("No attribution token available")
return
}
// Send to your server for Apple API validation
validateAttribution(token: token)
}
func validateAttribution(token: String) {
let endpoint = "https://api-adservices.apple.com/api/v1/"
var request = URLRequest(url: URL(string: endpoint)!)
request.httpMethod = "POST"
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpBody = token.data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
if let attribution = try? JSONDecoder().decode(AttributionResponse.self, from: data) {
print("Attribution: \(attribution)")
// Store attribution data
}
}.resume()
}
struct AttributionResponse: Codable {
let attribution: Bool
let orgId: Int?
let campaignId: Int?
let conversionType: String?
let adGroupId: Int?
let countryOrRegion: String?
let keywordId: Int?
let adId: Int?
}
In-App Purchase Events
Track revenue events with SKAdNetwork:
import StoreKit
func trackPurchase(product: SKProduct, quantity: Int) {
let revenue = product.price.doubleValue * Double(quantity)
let conversionValue = mapRevenueToConversionValue(revenue)
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(conversionValue)
}
// Also send to your analytics
logPurchaseEvent(product: product, quantity: quantity, revenue: revenue)
}
func mapRevenueToConversionValue(_ revenue: Double) -> Int {
// Map revenue to 0-63 scale for SKAdNetwork
switch revenue {
case 0..<1: return 0
case 1..<5: return 1
case 5..<10: return 2
case 10..<20: return 3
case 20..<50: return 4
case 50..<100: return 5
default: return 6
}
}
Registration / Sign Up
Track user registration as conversion:
func trackRegistration() {
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(10) // Custom value for registration
}
// Send to analytics
logEvent("user_registration", parameters: [
"method": "email",
"timestamp": Date()
])
}
Tutorial Completion
Track onboarding completion:
func trackTutorialComplete() {
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(15) // Custom value for tutorial
}
logEvent("tutorial_complete", parameters: [
"steps_completed": 5,
"time_spent": 120
])
}
SKAdNetwork Conversion Value Schema
Basic Schema (iOS 14.0+)
Define a conversion value mapping for 6-bit values (0-63):
enum ConversionEvent: Int {
case install = 0
case appOpen = 1
case registration = 10
case tutorialComplete = 15
case addToCart = 20
case initiateCheckout = 25
case purchase = 30
case subscription = 35
case level5Complete = 40
case level10Complete = 45
case highValuePurchase = 50
case retention3Day = 55
case retention7Day = 60
}
func updateConversionValue(event: ConversionEvent) {
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(event.rawValue)
}
}
Advanced Schema with Revenue Tiers (iOS 16.1+)
@available(iOS 16.1, *)
func trackRevenueEvent(revenue: Double, eventType: String) {
let fineValue = calculateFineValue(revenue: revenue, eventType: eventType)
let coarseValue = calculateCoarseValue(revenue: revenue)
SKAdNetwork.updatePostbackConversionValue(
fineValue: fineValue,
coarseValue: coarseValue
) { error in
if let error = error {
print("Failed to update conversion: \(error)")
}
}
}
@available(iOS 16.1, *)
func calculateCoarseValue(revenue: Double) -> SKAdNetwork.CoarseConversionValue {
switch revenue {
case 0..<10:
return .low
case 10..<50:
return .medium
default:
return .high
}
}
func calculateFineValue(revenue: Double, eventType: String) -> Int {
// Encode both event type and revenue into 6 bits
let eventBits = eventTypeBits(eventType) << 3 // 3 bits for event type
let revenueBits = revenueTierBits(revenue) // 3 bits for revenue
return eventBits | revenueBits
}
func eventTypeBits(_ eventType: String) -> Int {
switch eventType {
case "purchase": return 0
case "subscription": return 1
case "add_to_cart": return 2
case "registration": return 3
default: return 7
}
}
func revenueTierBits(_ revenue: Double) -> Int {
switch revenue {
case 0..<5: return 0
case 5..<10: return 1
case 10..<20: return 2
case 20..<50: return 3
case 50..<100: return 4
default: return 5
}
}
Custom Event Tracking
Level Completion (Gaming)
func trackLevelComplete(level: Int, score: Int) {
let conversionValue = min(level, 63) // Cap at max SKAdNetwork value
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(conversionValue)
}
logEvent("level_complete", parameters: [
"level": level,
"score": score,
"duration": calculateLevelDuration()
])
}
Content View (News/Media)
func trackContentView(articleId: String, category: String) {
// Use different conversion values for different content categories
let conversionValue: Int
switch category {
case "premium":
conversionValue = 30
case "standard":
conversionValue = 20
default:
conversionValue = 10
}
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(conversionValue)
}
logEvent("content_view", parameters: [
"article_id": articleId,
"category": category
])
}
Subscription Events
func trackSubscription(tier: String, price: Double, period: String) {
let conversionValue: Int
switch tier {
case "premium":
conversionValue = 60
case "standard":
conversionValue = 50
case "basic":
conversionValue = 40
default:
conversionValue = 35
}
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(conversionValue)
}
logEvent("subscribe", parameters: [
"tier": tier,
"price": price,
"period": period
])
}
Time-Based Conversion Values
D1, D3, D7 Retention
class RetentionTracker {
static let installDate = "install_date"
func trackRetention() {
guard let installDate = UserDefaults.standard.object(forKey: RetentionTracker.installDate) as? Date else {
// First launch - store install date
UserDefaults.standard.set(Date(), forKey: RetentionTracker.installDate)
return
}
let daysSinceInstall = Calendar.current.dateComponents([.day], from: installDate, to: Date()).day ?? 0
let conversionValue: Int
switch daysSinceInstall {
case 1:
conversionValue = 55 // D1 retention
case 3:
conversionValue = 58 // D3 retention
case 7:
conversionValue = 60 // D7 retention
default:
return
}
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(conversionValue)
}
}
}
MMP Event Tracking
AppsFlyer Events
import AppsFlyerLib
func trackAFEvent(eventName: String, revenue: Double? = nil) {
var eventValues: [String: Any] = [:]
if let revenue = revenue {
eventValues[AFEventParamRevenue] = revenue
eventValues[AFEventParamCurrency] = "USD"
}
AppsFlyerLib.shared().logEvent(eventName, withValues: eventValues)
}
// Purchase event
func trackPurchaseWithAF(orderId: String, revenue: Double, items: [String]) {
trackAFEvent(
eventName: AFEventPurchase,
revenue: revenue
)
}
Adjust Events
import Adjust
func trackAdjustEvent(token: String, revenue: Double? = nil) {
let event = ADJEvent(eventToken: token)
if let revenue = revenue {
event?.setRevenue(revenue, currency: "USD")
}
Adjust.trackEvent(event)
}
Testing & Validation
Test Mode (Development)
#if DEBUG
func testSKAdNetworkPostback() {
// In development, conversions update faster
if #available(iOS 14.0, *) {
SKAdNetwork.updateConversionValue(99) // Test value
print("Test conversion value sent")
}
}
#endif
Logging for Debug
func logConversionEvent(event: String, value: Int) {
print("Conversion Event: \(event), Value: \(value)")
#if DEBUG
// Additional debug logging
let debugInfo = """
Event: \(event)
Value: \(value)
Timestamp: \(Date())
User: \(getCurrentUserId())
"""
print(debugInfo)
#endif
}
Best Practices
- Update SKAdNetwork conversion value within 24 hours of install for optimal attribution
- Use progressive conversion values (lower for early events, higher for valuable actions)
- Document your conversion value schema for team reference
- Test conversion values in TestFlight before production release
- Balance between granularity and privacy in conversion mapping
- Consider time-based events for retention measurement
- Implement both SKAdNetwork and MMP tracking for comprehensive attribution
- Monitor conversion value distribution in your analytics