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

# Push Notification Tracking

> Push Notification Tracking — Zixflow iOS SDK integration guide.

### Overview

Zixflow automatically tracks push notification delivery and engagement metrics to help you measure campaign performance. The SDK tracks three key lifecycle events:

1. **Delivery Confirmed** - When the notification arrives on the device
2. **Notification Opened** - When the user taps the notification
3. **Action Button Clicked** - When the user taps an action button

### How Push Tracking Works

When Zixflow sends a push notification, it includes special tracking fields in the data payload:

| Field                    | Purpose                                                |
| ------------------------ | ------------------------------------------------------ |
| `Zixflow-Delivery-ID`    | Unique ID for this delivery (links to campaign record) |
| `Zixflow-Delivery-Token` | The APNs device token the notification was sent to     |

**Example push payload:**

```json theme={null}
{
  "Zixflow-Delivery-ID": "626533406292836846",
  "Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
  "aps": {
    "alert": {
      "title": "Flash Sale! 70% OFF",
      "body": "Limited time only — grab your deal now!"
    },
    "mutable-content": 1
  },
  "zixflow": {
    "push": {
      "link": "https://yourapp.com/sale",
      "image": "https://cdn.yourapp.com/banner.png",
      "action_buttons": "[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"
    }
  }
}
```

> **Note:** The SDK automatically handles most tracking when using the Notification Service Extension. This section covers advanced customization scenarios.

***

### Tracking Events

#### 1. Delivery Confirmed

**When to track:** In the Notification Service Extension when the push payload arrives

**SDK method:** `MessagingPush.shared.trackMetric(deliveryID:, event:, deviceToken:)`

**Implementation (NotificationService.swift):**

```swift theme={null}
import UserNotifications
import ZixflowMessagingPushAPN

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        // Initialize SDK for extension
        MessagingPush.initialize(
            withConfig: MessagingPushConfigBuilder(apiKey: "YOUR_API_KEY")
                .build()
        )

        if let bestAttemptContent = bestAttemptContent {
            // Let Zixflow handle rich push and track delivery
            MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()

        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}
```

> **Important:** The SDK automatically tracks delivery when using the Notification Service Extension.

***

#### 2. Notification Opened

**When to track:** When the user taps the notification

**SDK method:** Handled automatically by `MessagingPush.shared.didReceive(_:)`

**Implementation (AppDelegate.swift):**

```swift theme={null}
import UIKit
import UserNotifications
import ZixflowMessagingPushAPN

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Initialize Zixflow push messaging
        MessagingPush.initialize(
            withConfig: MessagingPushConfigBuilder()
                .showPushAppInForeground(true)
                .build()
        )

        // Set notification delegate
        UNUserNotificationCenter.current().delegate = self

        return true
    }

    // Handle notification tap
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        // Zixflow automatically tracks opens
        MessagingPush.shared.userNotificationCenter(
            center,
            didReceive: response,
            withCompletionHandler: completionHandler
        )
    }

    // Handle foreground presentation
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler([.banner, .list, .badge, .sound])
    }
}
```

> **Note:** The SDK automatically tracks opens when using the default delegate implementation.

***

#### 3. Action Button Clicked

**When to track:** When the user taps a named action button

**SDK method:** Automatically tracked in `userNotificationCenter(_:didReceive:withCompletionHandler:)`

**Action button properties:**

| Property                 | Type    | Required    | Description                      |
| ------------------------ | ------- | ----------- | -------------------------------- |
| `Zixflow-Delivery-ID`    | string  | ✅           | Links event to campaign delivery |
| `action_index`           | integer | ✅           | 0-based index of button tapped   |
| `action_name`            | string  | ✅           | Button label (e.g., "Shop Now")  |
| `Zixflow-Delivery-Token` | string  | Recommended | APNs device token                |
| `action_deeplink`        | string  | Recommended | Button's URL                     |

***

### Action Buttons Format

The `action_buttons` field in the push payload is a JSON-encoded string:

**Raw payload value:**

```
"[{\"name\":\"Shop Now\",\"deeplink\":\"https://yourapp.com/sale\"},{\"name\":\"Remind Me\",\"deeplink\":\"\"}]"
```

**Parsed structure:**

```json theme={null}
[
  { "name": "Shop Now",  "deeplink": "https://yourapp.com/sale" },
  { "name": "Remind Me", "deeplink": "" }
]
```

**Parsing example:**

```swift theme={null}
import Foundation

struct ActionButton: Codable {
    let name: String
    let deeplink: String
}

func parseActionButtons(from jsonString: String?) -> [ActionButton] {
    guard let jsonString = jsonString,
          let data = jsonString.data(using: .utf8) else {
        return []
    }

    do {
        let buttons = try JSONDecoder().decode([ActionButton].self, from: data)
        return buttons
    } catch {
        print("Error parsing action buttons: \(error)")
        return []
    }
}

// Usage
let buttonsJSON = notification.request.content.userInfo["action_buttons"] as? String
let buttons = parseActionButtons(from: buttonsJSON)

for (index, button) in buttons.enumerated() {
    print("Button \(index): \(button.name) -> \(button.deeplink)")
}
```

**Rules:**

* Maximum 2-4 buttons per notification (iOS limitation)
* `deeplink` may be empty — handle gracefully
* Button index is 0-based (first button = index 0)

***

### Complete Tracking Implementation

Here's a complete AppDelegate with all tracking integrated:

```swift theme={null}
import UIKit
import UserNotifications
import ZixflowMessagingPushAPN
import ZixflowDataPipelines

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Initialize Zixflow SDK
        Zixflow.initialize(
            apiKey: "YOUR_API_KEY",
            config: ZixflowConfigBuilder()
                .logLevel(.debug)
                .build()
        )

        // Initialize push messaging
        MessagingPush.initialize(
            withConfig: MessagingPushConfigBuilder()
                .showPushAppInForeground(true)
                .build()
        )

        // Set notification delegate
        UNUserNotificationCenter.current().delegate = self

        // Request push permission
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert, .badge, .sound]
        ) { granted, error in
            if granted {
                DispatchQueue.main.async {
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
        }

        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        // Register device token with Zixflow
        MessagingPush.shared.application(
            application,
            didRegisterForRemoteNotificationsWithDeviceToken: deviceToken
        )
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse,
        withCompletionHandler completionHandler: @escaping () -> Void
    ) {
        // Zixflow automatically tracks:
        // 1. Opened event
        // 2. Action button click (if action tapped)
        MessagingPush.shared.userNotificationCenter(
            center,
            didReceive: response,
            withCompletionHandler: completionHandler
        )
    }

    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        // Show notification in foreground
        completionHandler([.banner, .list, .badge, .sound])
    }
}
```

***

### Tracking Decision Flowchart

```
Notification received (in NSE)?
├── YES → trackMetric(delivered)        [automatic with SDK]
│         Download rich media (images)
│         Display notification
│
User interacted?
├── Tapped notification body
│   └── trackMetric(opened)             [automatic with SDK]
│       └── Navigate to deeplink
│
└── Tapped action button
    ├── trackMetric(opened)             [automatic with SDK]
    └── track("Push Notification Action Clicked")
        └── Navigate to button's deeplink
```

***

### App Groups for Delivery Tracking

To ensure delivery metrics are accurately tracked, configure App Groups:

**1. Enable App Groups in Xcode:**

1. Select your main app target

2. Go to **Signing & Capabilities**

3. Click **+ Capability** → Add **App Groups**

4. Create a group: `group.com.yourcompany.yourapp.zixflow`

5. Repeat for Notification Service Extension target

**2. Configure in MessagingPush:**

```swift theme={null}
MessagingPush.initialize(
    withConfig: MessagingPushConfigBuilder()
        .appGroup("group.com.yourcompany.yourapp.zixflow")
        .build()
)
```

This allows the Notification Service Extension to persist delivery metrics that the main app can report when launched.

***

### Fallback for Non-Zixflow Push

If a push notification doesn't contain `Zixflow-Delivery-ID` (sent from another source), the SDK automatically skips tracking.

For custom analytics on non-Zixflow pushes, use a different event name:

```swift theme={null}
// Don't use reserved push event names for non-Zixflow pushes
Zixflow.instance().track(
    name: "External Push Received",
    properties: [
        "notification_id": notification.request.identifier,
        "title": notification.request.content.title,
        "body": notification.request.content.body,
        "source": "external"
    ]
)
```

> **Important:** Push notification event names (`Push Notification Delivered`, `Push Notification Opened`, `Push Notification Action Clicked`) are reserved for Zixflow's delivery pipeline.

***

### Testing Push Tracking

#### Verify Tracking Events

Enable debug logging to see tracking events:

```swift theme={null}
Zixflow.initialize(
    apiKey: "YOUR_API_KEY",
    config: ZixflowConfigBuilder()
        .logLevel(.debug)
        .build()
)
```

Look for these log messages in Xcode console:

* `[Zixflow] Push Notification Delivered tracked`
* `[Zixflow] Push Notification Opened tracked`
* `[Zixflow] Push Notification Action Clicked tracked`

#### Check Campaign Analytics

1. Log into Zixflow dashboard
2. Navigate to **Messaging** → **Push Notifications**
3. View campaign metrics:
   * **Sent** - Total notifications sent
   * **Delivered** - Arrived on device
   * **Opened** - User tapped notification
   * **Clicked** - User tapped action button

***
