> ## 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 JavaScript SDK integration guide.

### Overview

Zixflow automatically tracks web 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 push subscription endpoint                         |

**Example push payload:**

```json theme={null}
{
  "Zixflow-Delivery-ID": "626533406292836846",
  "Zixflow-Delivery-Token": "https://fcm.googleapis.com/...",
  "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:** When using the Zixflow service worker implementation, tracking is handled automatically. This section covers custom implementations.

***

### Tracking Events

#### 1. Delivery Confirmed

**When to track:** The moment the push event fires in the service worker

**Implementation (service-worker.js):**

```javascript theme={null}
// service-worker.js
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  if (deliveryId && token) {
    // Track delivery using fetch API
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Delivered',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
        },
      }),
    }).catch(() => {});
  }

  // Show notification
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Notification', {
      body: data.body ?? '',
      icon: data.image_url,
      data: data,
      actions: parseButtons(data.action_buttons),
    })
  );
});

function parseButtons(raw) {
  if (!raw) return [];
  try {
    const decoded = typeof raw === 'string' ? JSON.parse(raw) : raw;
    return decoded.slice(0, 2).map((btn, i) => ({
      action: `ACTION_${i}`,
      title: btn.name || `Action ${i + 1}`,
    }));
  } catch {
    return [];
  }
}
```

***

#### 2. Notification Opened

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

**Implementation (service-worker.js):**

```javascript theme={null}
// service-worker.js
self.addEventListener('notificationclick', (event) => {
  const data = event.notification.data ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  event.notification.close();

  // Track opened
  if (deliveryId && token) {
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Opened',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
        },
      }),
    }).catch(() => {});
  }

  // Handle action button click
  if (event.action) {
    const actionIndex = parseInt(event.action.replace(/\D/g, ''), 10);
    const buttons = parseButtons(data.action_buttons);
    const actionName = buttons[actionIndex]?.name ?? '';
    const deeplink = buttons[actionIndex]?.deeplink ?? '';

    // Track action click
    fetch('https://events.zixflow.in/v1/track', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`
      },
      body: JSON.stringify({
        event: 'Push Notification Action Clicked',
        userId: self.__ZIXFLOW_USER_ID__,
        properties: {
          'Zixflow-Delivery-ID': deliveryId,
          'Zixflow-Delivery-Token': token,
          'action_index': actionIndex,
          'action_name': actionName,
          'action_deeplink': deeplink,
          'source': 'web_push',
        },
      }),
    }).catch(() => {});

    if (deeplink) {
      event.waitUntil(clients.openWindow(deeplink));
      return;
    }
  }

  // Default: open deeplink or focus existing tab
  const deeplink = data['deeplink_url'] ?? '';
  event.waitUntil(
    deeplink ? clients.openWindow(deeplink) : clients.openWindow('/')
  );
});
```

***

#### 3. Action Button Clicked

Action button clicks are tracked within the `notificationclick` event handler shown above when `event.action` is present.

**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 | Push subscription endpoint       |
| `action_deeplink`        | string  | Recommended | Button's URL                     |
| `source`                 | string  | Optional    | Set to "web\_push"               |

***

### Complete Service Worker Implementation

Here's a complete service worker with all tracking:

```javascript theme={null}
// public/zixflow-sw.js
const WRITE_KEY = 'YOUR_API_KEY';
const ZIXFLOW_API = 'https://events.zixflow.in/v1/track';

// Store user ID (set from main app)
self.__ZIXFLOW_USER_ID__ = null;

// Listen for messages from main app
self.addEventListener('message', (event) => {
  if (event.data?.type === 'SET_USER_ID') {
    self.__ZIXFLOW_USER_ID__ = event.data.userId;
  }
});

// Track delivery
self.addEventListener('push', (event) => {
  const data = event.data?.json() ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  // Track delivery
  if (deliveryId && token) {
    trackEvent('Push Notification Delivered', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
    });
  }

  // Parse action buttons
  const buttons = parseActionButtons(data.action_buttons);

  // Show notification
  event.waitUntil(
    self.registration.showNotification(data.title ?? 'Notification', {
      body: data.body ?? '',
      icon: data.image_url,
      badge: data.badge_url,
      data: data,
      actions: buttons,
    })
  );
});

// Track opens and action clicks
self.addEventListener('notificationclick', (event) => {
  const data = event.notification.data ?? {};
  const deliveryId = data['Zixflow-Delivery-ID'] ?? '';
  const token = data['Zixflow-Delivery-Token'] ?? '';

  event.notification.close();

  // Always track opened
  if (deliveryId && token) {
    trackEvent('Push Notification Opened', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
    });
  }

  // Handle action button click
  if (event.action) {
    const actionIndex = parseInt(event.action.replace(/\D/g, ''), 10);
    const rawButtons = parseActionButtonsRaw(data.action_buttons);
    const actionName = rawButtons[actionIndex]?.name ?? '';
    const actionDeeplink = rawButtons[actionIndex]?.deeplink ?? '';

    trackEvent('Push Notification Action Clicked', {
      'Zixflow-Delivery-ID': deliveryId,
      'Zixflow-Delivery-Token': token,
      'action_index': actionIndex,
      'action_name': actionName,
      'action_deeplink': actionDeeplink,
      'source': 'web_push',
    });

    if (actionDeeplink) {
      event.waitUntil(clients.openWindow(actionDeeplink));
      return;
    }
  }

  // Default navigation
  const deeplink = data['deeplink_url'] ?? '';
  event.waitUntil(
    deeplink ? clients.openWindow(deeplink) : clients.openWindow('/')
  );
});

// Helper: Track event
function trackEvent(eventName, properties) {
  fetch(ZIXFLOW_API, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Basic ${btoa(WRITE_KEY + ':')}`,
    },
    body: JSON.stringify({
      event: eventName,
      userId: self.__ZIXFLOW_USER_ID__,
      properties: properties,
    }),
  }).catch((err) => {
    console.error('Failed to track event:', err);
  });
}

// Helper: Parse action buttons for notification API
function parseActionButtons(raw) {
  const buttons = parseActionButtonsRaw(raw);
  return buttons.slice(0, 2).map((btn, i) => ({
    action: `ACTION_${i}`,
    title: btn.name || `Action ${i + 1}`,
  }));
}

// Helper: Parse raw action buttons data
function parseActionButtonsRaw(raw) {
  if (!raw) return [];
  try {
    return typeof raw === 'string' ? JSON.parse(raw) : raw;
  } catch {
    return [];
  }
}
```

***

### Setting User ID in Service Worker

To associate tracking events with the correct user, pass the user ID from your main application to the service worker:

```javascript theme={null}
// main app (after analytics.identify())
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
  navigator.serviceWorker.controller.postMessage({
    type: 'SET_USER_ID',
    userId: 'user@example.com',
  });
}
```

***

### Action Buttons Format

The `action_buttons` field 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": "" }
]
```

**Rules:**

* Maximum 2 buttons (browser limitation)
* `deeplink` may be empty
* Button index is 0-based

***

### Tracking Decision Flowchart

```
Notification received (push event)?
├── YES → Track "Push Notification Delivered"
│         Show notification
│
User clicked notification?
├── Clicked body
│   └── Track "Push Notification Opened"
│       Navigate to deeplink_url
│
└── Clicked action button
    ├── Track "Push Notification Opened"
    └── Track "Push Notification Action Clicked"
        Navigate to button's deeplink
```

***

### Fallback for Non-Zixflow Push

If a push doesn't contain `Zixflow-Delivery-ID`, skip tracking or use custom event names:

```javascript theme={null}
// Don't use reserved push event names for non-Zixflow pushes
if (!data['Zixflow-Delivery-ID']) {
  trackEvent('External Push Received', {
    'title': data.title ?? '',
    'body': data.body ?? '',
    'source': 'external',
  });
}
```

> **Important:** Event names `Push Notification Delivered`, `Push Notification Opened`, and `Push Notification Action Clicked` are reserved for Zixflow's delivery pipeline.

***

### Testing Push Tracking

#### Enable Debug Logging

Add logging to your service worker:

```javascript theme={null}
function trackEvent(eventName, properties) {
  console.log('[Zixflow] Tracking:', eventName, properties);

  fetch(ZIXFLOW_API, {
    // ... existing implementation
  }).then(response => {
    console.log('[Zixflow] Track response:', response.status);
  }).catch((err) => {
    console.error('[Zixflow] Track failed:', err);
  });
}
```

View logs in browser DevTools → Application → Service Workers → Console

#### Check Campaign Analytics

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

***
