> ## 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 Android 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 FCM token the notification was sent to             |

**Example push payload:**

```json theme={null}
{
  "Zixflow-Delivery-ID": "626533406292836846",
  "Zixflow-Delivery-Token": "dcFRlDhiRbehM1vg...",
  "title": "Flash Sale! 70% OFF",
  "body": "Limited time only — grab your deal now!",
  "deeplink_url": "https://yourapp.com/sale",
  "image_url": "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 for you. This section covers advanced customization scenarios.

***

### Tracking Events

#### 1. Delivery Confirmed

**When to track:** The moment the push payload arrives on the device

**SDK method:** `trackMetric(deliveryID, deviceToken, event = MetricEvent.DELIVERED)`

**Basic implementation (in FirebaseMessagingService):**

```kotlin theme={null}
override fun onMessageReceived(message: RemoteMessage) {
    val deliveryId    = message.data["Zixflow-Delivery-ID"] ?: ""
    val deliveryToken = message.data["Zixflow-Delivery-Token"] ?: cachedToken ?: ""

    if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
        Zixflow.instance.trackMetric(
            deliveryID  = deliveryId,
            deviceToken = deliveryToken,
            event       = MetricEvent.DELIVERED
        )
    }
}
```

> **Important:** The Zixflow SDK automatically tracks delivery when using `ModuleMessagingPushFCM`. Manual tracking is only needed if you have a custom `FirebaseMessagingService` implementation.

***

#### 2. Notification Opened

**When to track:** When the user taps the notification banner (body or action button)

**SDK method:** `trackMetric(deliveryID, deviceToken, event = MetricEvent.OPENED)`

**Implementation (in launcher Activity):**

```kotlin theme={null}
// In your launcher Activity.onCreate() or onNewIntent()
intent.extras?.let { extras ->
    val deliveryId    = extras.getString("Zixflow-Delivery-ID", "")
    val deliveryToken = extras.getString("Zixflow-Delivery-Token") ?: cachedToken ?: ""

    if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
        Zixflow.instance.trackMetric(
            deliveryID  = deliveryId,
            deviceToken = deliveryToken,
            event       = MetricEvent.OPENED
        )
    }
}
```

**Also handle in onNewIntent():**

```kotlin theme={null}
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.extras?.let { extras ->
        val deliveryId    = extras.getString("Zixflow-Delivery-ID", "")
        val deliveryToken = extras.getString("Zixflow-Delivery-Token") ?: cachedToken ?: ""

        if (deliveryId.isNotEmpty() && deliveryToken.isNotEmpty()) {
            Zixflow.instance.trackMetric(
                deliveryID  = deliveryId,
                deviceToken = deliveryToken,
                event       = MetricEvent.OPENED
            )
        }
    }
}
```

> **Note:** The SDK automatically tracks opens when using the default push implementation. This is only needed for custom implementations.

***

#### 3. Action Button Clicked

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

**SDK method:** `track("Push Notification Action Clicked", properties)`

**Implementation (in NotificationActionReceiver):**

```kotlin theme={null}
// In your BroadcastReceiver or Activity that handles action taps
val deliveryId    = intent.getStringExtra("Zixflow-Delivery-ID") ?: ""
val deliveryToken = intent.getStringExtra("Zixflow-Delivery-Token") ?: cachedToken ?: ""
val actionIndex   = intent.getIntExtra("action_index", -1)
val actionName    = intent.getStringExtra("action_name") ?: "Action ${actionIndex + 1}"
val actionDeeplink = intent.getStringExtra("action_deeplink") ?: ""

// Always fire opened first
Zixflow.instance.trackMetric(
    deliveryID  = deliveryId,
    deviceToken = deliveryToken,
    event       = MetricEvent.OPENED
)

// Then fire action clicked
Zixflow.instance.track(
    name = "Push Notification Action Clicked",
    properties = mapOf(
        "Zixflow-Delivery-ID"    to deliveryId,
        "Zixflow-Delivery-Token" to deliveryToken,
        "action_index"           to actionIndex,
        "action_name"            to actionName,
        "action_deeplink"        to actionDeeplink
    )
)
```

**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 | FCM token                        |
| `action_deeplink`        | string  | Recommended | Button's URL                     |

***

### Action Buttons Format

The `action_buttons` field is a JSON-encoded string containing button definitions:

**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:**

```kotlin theme={null}
import org.json.JSONArray

fun parseActionButtons(buttonsJson: String?): List<ActionButton> {
    if (buttonsJson.isNullOrEmpty()) return emptyList()

    return try {
        val jsonArray = JSONArray(buttonsJson)
        List(jsonArray.length()) { index ->
            val button = jsonArray.getJSONObject(index)
            ActionButton(
                name = button.getString("name"),
                deeplink = button.optString("deeplink", "")
            )
        }
    } catch (e: Exception) {
        emptyList()
    }
}

data class ActionButton(
    val name: String,
    val deeplink: String
)
```

**Rules:**

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

***

### Handling Existing FirebaseMessagingService

If your app already has a custom `FirebaseMessagingService`, delegate Zixflow push handling:

```kotlin theme={null}
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.zixflow.messagingpush.ZixflowFirebaseMessagingService

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        // Let Zixflow SDK handle push notifications
        val handled = ZixflowFirebaseMessagingService.onMessageReceived(
            context = this,
            remoteMessage = message
        )

        if (!handled) {
            // Handle non-Zixflow push notifications
            handleCustomPush(message)
        }
    }

    override fun onNewToken(token: String) {
        super.onNewToken(token)

        // Register new token with Zixflow
        ZixflowFirebaseMessagingService.onNewToken(
            context = this,
            token = token
        )

        // Handle token for other services
        handleCustomToken(token)
    }

    private fun handleCustomPush(message: RemoteMessage) {
        // Your custom push handling logic
    }

    private fun handleCustomToken(token: String) {
        // Your custom token handling logic
    }
}
```

Register your service in `AndroidManifest.xml`:

```xml theme={null}
<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
```

***

### Tracking Decision Flowchart

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

***

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

```kotlin theme={null}
// Don't use reserved push event names for non-Zixflow pushes
Zixflow.instance.track(
    name = "External Push Received",
    properties = mapOf(
        "notification_id" to (message.messageId ?: ""),
        "title" to (message.data["title"] ?: ""),
        "body" to (message.data["body"] ?: ""),
        "source" to "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:

```kotlin theme={null}
val config = ZixflowConfigBuilder(this, "YOUR_API_KEY")
    .logLevel(ZixflowLogLevel.DEBUG)
    .build()
```

Look for these log messages:

* `[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

***
