> ## Documentation Index
> Fetch the complete documentation index at: https://docs.permutive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Tracking

> Track custom events directly with the Permutive SDK

While `PageTracker` is the recommended approach for most tracking, you can also track custom events directly using the `track(event:properties:)` method.

<CardGroup cols={2}>
  <Card title="Basic Tracking" href="#tracking-events" icon="bolt" />

  <Card title="Schema Validation" href="#schema-validation" icon="check" />
</CardGroup>

## 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

<Tip>
  For view-based tracking with engagement time, scroll depth, and contextual cohorts, use [PageTracker](/sdks/mobile/ios/features/page-tracking) instead.
</Tip>

## Tracking Events

### Basic Event

<CodeGroup>
  ```swift Swift theme={"dark"}
  do {
      try Permutive.shared.track(event: "AppLaunch")
  } catch {
      print("Failed to track event: \(error)")
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  NSError *error = nil;
  [Permutive.shared trackWithEvent:@"AppLaunch" properties:nil error:&error];
  if (error != nil) {
      NSLog(@"Failed to track event: %@", error);
  }
  ```
</CodeGroup>

### Event with Properties

<CodeGroup>
  ```swift Swift theme={"dark"}
  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)")
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  NSError *error = nil;
  PermutiveEventProperties *properties = [[PermutiveEventProperties alloc]
      init:@{
          @"button_name": @"subscribe",
          @"position": @"header",
          @"is_premium": @YES
      }
      error:&error];

  [Permutive.shared trackWithEvent:@"ButtonClick" properties:properties error:&error];
  ```
</CodeGroup>

## 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

<Tabs>
  <Tab title="Valid Names">
    ```swift theme={"dark"}
    "AppLaunch"           // ✅
    "button_click"        // ✅
    "VideoPlay2"          // ✅
    "user_subscription"   // ✅
    ```
  </Tab>

  <Tab title="Invalid Names">
    ```swift theme={"dark"}
    "button-click"        // ❌ Hyphen not allowed
    "2_event"             // ❌ Cannot start with number
    "my event"            // ❌ Spaces not allowed
    "événement"           // ❌ Non-ASCII characters
    ```
  </Tab>
</Tabs>

## Schema Validation

<Warning>
  Event properties **must match exactly** the schema defined in your Permutive dashboard. Mismatched properties cause event rejection.
</Warning>

### Enable Debug Logging

```swift theme={"dark"}
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

<CodeGroup>
  ```swift Swift theme={"dark"}
  // 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")
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  // App launch
  - (void)applicationDidBecomeActive:(UIApplication *)application {
      NSError *error = nil;
      PermutiveEventProperties *props = [[PermutiveEventProperties alloc]
          init:@{@"launch_type": self.isFirstLaunch ? @"fresh" : @"resume"}
          error:&error];
      [Permutive.shared trackWithEvent:@"AppActive" properties:props error:&error];
  }
  ```
</CodeGroup>

### User Action Events

<CodeGroup>
  ```swift Swift theme={"dark"}
  // 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
          ])
      )
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  // Button click
  - (void)onSubscribeButtonTapped {
      NSError *error = nil;
      PermutiveEventProperties *props = [[PermutiveEventProperties alloc]
          init:@{@"plan": @"premium", @"source": @"paywall"}
          error:&error];
      [Permutive.shared trackWithEvent:@"SubscribeClick" properties:props error:&error];
  }
  ```
</CodeGroup>

### Push Notification Events

```swift theme={"dark"}
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

<CodeGroup>
  ```swift Swift theme={"dark"}
  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 ?? ""
          ])
      )
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  - (void)onPurchaseCompleteWithProduct:(SKProduct *)product
                            transaction:(SKPaymentTransaction *)transaction {
      NSError *error = nil;
      PermutiveEventProperties *props = [[PermutiveEventProperties alloc]
          init:@{
              @"product_id": product.productIdentifier,
              @"price": product.price,
              @"currency": product.priceLocale.currencyCode ?: @"USD",
              @"transaction_id": transaction.transactionIdentifier ?: @""
          }
          error:&error];
      [Permutive.shared trackWithEvent:@"Purchase" properties:props error:&error];
  }
  ```
</CodeGroup>

## Event Enrichment

Add server-side enrichment to events:

```swift theme={"dark"}
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](/sdks/mobile/ios/core-concepts/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

<CodeGroup>
  ```swift Swift theme={"dark"}
  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)")
      }
  }
  ```

  ```objectivec Objective-C theme={"dark"}
  - (void)trackEvent:(NSString *)name properties:(NSDictionary *)properties {
      NSError *error = nil;
      PermutiveEventProperties *eventProps = [[PermutiveEventProperties alloc]
          init:properties
          error:&error];

      if (error != nil) {
          NSLog(@"Failed to create properties: %@", error);
          return;
      }

      [Permutive.shared trackWithEvent:name properties:eventProps error:&error];

      if (error != nil) {
          NSLog(@"Event tracking failed: %@", error);
      }
  }
  ```
</CodeGroup>

## Best Practices

<Tabs>
  <Tab title="Do">
    * 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
  </Tab>

  <Tab title="Don't">
    * 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)
  </Tab>
</Tabs>

## Troubleshooting

<AccordionGroup>
  <Accordion title="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
  </Accordion>

  <Accordion title="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
  </Accordion>

  <Accordion title="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
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Page Tracking" icon="file" href="/sdks/mobile/ios/features/page-tracking">
    View-based tracking with PageTracker
  </Card>

  <Card title="Event Properties" icon="list" href="/sdks/mobile/ios/core-concepts/event-properties">
    Structuring event metadata
  </Card>

  <Card title="Video Tracking" icon="video" href="/sdks/mobile/ios/features/video-tracking">
    Track video content
  </Card>

  <Card title="Issues" icon="triangle-exclamation" href="/sdks/mobile/ios/troubleshooting/common-errors">
    Common problems and solutions
  </Card>
</CardGroup>
