While PageTracker is the recommended approach for most tracking, you can also track custom events directly using the track(event:properties:) method.
When to Use Direct Event Tracking
Use direct event tracking for:
Events that don’t fit the page view model
Background events (app state changes, push notifications)
One-off events without context
Events outside of a view lifecycle
For view-based tracking with engagement time, scroll depth, and contextual cohorts, use PageTracker instead.
Tracking Events
Basic Event
do {
try Permutive. shared . track ( event : "AppLaunch" )
} catch {
print ( "Failed to track event: \( error ) " )
}
Event with Properties
do {
let properties = try EventProperties ([
"button_name" : "subscribe" ,
"position" : "header" ,
"is_premium" : true
])
try Permutive. shared . track ( event : "ButtonClick" , properties : properties)
} catch {
print ( "Failed to track event: \( error ) " )
}
Event Naming Rules
Event names must follow these rules:
Only alphanumeric characters and underscores: [a-zA-Z0-9_]
Cannot start with a number
Case-sensitive
Valid Names
Invalid Names
"AppLaunch" // ✅
"button_click" // ✅
"VideoPlay2" // ✅
"user_subscription" // ✅
"button-click" // ❌ Hyphen not allowed
"2_event" // ❌ Cannot start with number
"my event" // ❌ Spaces not allowed
"événement" // ❌ Non-ASCII characters
Schema Validation
Event properties must match exactly the schema defined in your Permutive dashboard. Mismatched properties cause event rejection.
Enable Debug Logging
options. logModes = LogMode. all
Schema Error Example
When properties don’t match the schema:
Permutive: [error] Encountered 1 event rejection!
Permutive: [error] Error 1: A validation check carried out by the server did not succeed.(1007)
Permutive: [error] Error 1: Schema validation failed with reason(s): [#: extraneous key [invalid_key] is not permitted]
Checking Events Were Accepted
Successful events show:
Permutive: [info] Accepted: 1 / 1
Common Event Types
App Lifecycle Events
// App launch
func applicationDidBecomeActive () {
try ? Permutive. shared . track (
event : "AppActive" ,
properties : try ? EventProperties ([
"launch_type" : isFirstLaunch ? "fresh" : "resume"
])
)
}
// App background
func applicationWillResignActive () {
try ? Permutive. shared . track ( event : "AppBackground" )
}
User Action Events
// Button click
func onSubscribeButtonTapped () {
try ? Permutive. shared . track (
event : "SubscribeClick" ,
properties : try ? EventProperties ([
"plan" : "premium" ,
"source" : "paywall"
])
)
}
// Search
func onSearch ( query : String , resultCount : Int ) {
try ? Permutive. shared . track (
event : "Search" ,
properties : try ? EventProperties ([
"query_length" : query. count ,
"result_count" : resultCount,
"has_results" : resultCount > 0
])
)
}
Push Notification Events
func userNotificationCenter (
_ center : UNUserNotificationCenter,
didReceive response : UNNotificationResponse
) {
let userInfo = response. notification . request . content . userInfo
try ? Permutive. shared . track (
event : "PushNotificationOpen" ,
properties : try ? EventProperties ([
"campaign_id" : userInfo[ "campaign_id" ] as? String ?? "unknown" ,
"action" : response. actionIdentifier
])
)
}
Purchase Events
func onPurchaseComplete ( product : SKProduct, transaction : SKPaymentTransaction) {
try ? Permutive. shared . track (
event : "Purchase" ,
properties : try ? EventProperties ([
"product_id" : product. productIdentifier ,
"price" : product. price . doubleValue ,
"currency" : product. priceLocale . currencyCode ?? "USD" ,
"transaction_id" : transaction. transactionIdentifier ?? ""
])
)
}
Event Enrichment
Add server-side enrichment to events:
let properties = try EventProperties ([
"geo_info" : EventProperties. geoInfoValue ,
"isp_info" : EventProperties. ispInfoValue ,
"form_id" : "contact_form"
])
try Permutive. shared . track ( event : "FormSubmit" , properties : properties)
See Event Properties for all enrichment options.
Batching and Delivery
Events are:
Queued locally when tracked
Sent in batches to reduce network overhead
Persisted to disk if the app closes before sending
Retried automatically on network failure
You don’t need to manage batching - it’s handled automatically.
Error Handling
func trackEvent ( name : String , properties : [ String : Any ]) {
do {
let eventProperties = try EventProperties (properties)
try Permutive. shared . track ( event : name, properties : eventProperties)
} catch let error as NSError {
// Log but don't crash - tracking failures shouldn't break app
print ( "Event tracking failed: \( error. localizedDescription ) " )
// Optionally report to error tracking
// Crashlytics.log("Permutive track error: \(error)")
}
}
Best Practices
Use descriptive, consistent event names
Match your dashboard schema exactly
Test events with debug logging enabled
Handle errors gracefully
Use PageTracker for view-based tracking
Keep properties minimal and relevant
Don’t include PII in event properties
Don’t track too many fine-grained events
Don’t use dynamic event names not in your schema
Don’t block the UI waiting for tracking
Don’t track sensitive data (passwords, tokens)
Troubleshooting
Events not appearing in dashboard
Problem: Tracked events don’t show up.Solutions:
Enable debug logging and look for Accepted: X / X
Check for schema validation errors in logs
Wait up to 5 minutes for processing
Verify you’re in the correct workspace
EventProperties init fails
Problem: Creating EventProperties throws an error.Solutions:
Verify all values are supported types
Check for unsupported custom objects
Ensure arrays contain only supported types
Events rejected with schema error
Problem: Events sent but rejected by server.Solutions:
Check event name matches schema exactly
Verify property names and types
Remove properties not in the schema
Check for typos