# Push Events Table

Beta Feature
Mobile Push is currently in beta. The schema and implementation details described in this document are provided as examples and may change. You should design your own table schema based on your specific tracking requirements and business needs.

## Overview

When you implement Mobile Push notifications with Engage Studio, your mobile app tracks user interactions and sends event data to Treasure Data. This data is stored in a dedicated table (recommended name: `push_events`) within your Treasure Data database.

This document describes a sample schema, event types, and best practices for working with push notification event data. **Note:** The schema provided here is an example. You should customize it based on your application's requirements and data governance policies.

## Table Schema

### Sample Table Structure

Customize for Your Needs
The table structure shown below is a sample implementation. Design your own schema based on:

- Your tracking requirements
- Data governance and privacy policies
- Analytics and reporting needs
- Compliance requirements (GDPR, CCPA, etc.)


| Column Name | Data Type | Required | Description |
|  --- | --- | --- | --- |
| `time` | INTEGER | Yes | Unix timestamp (seconds) when the event occurred |
| `type` | STRING | Yes | Event type: `delivery`, `open`, `dismiss`, `link_open`, `deeplink_open`, `token_register` |
| `td_campaign_id` | STRING | Yes | Campaign identifier from Engage Studio (passed from notification payload) |
| `platform` | STRING | Yes | Mobile platform: `android` or `ios` |
| `fcm_token` | STRING | No | Firebase Cloud Messaging device token (required for `token_register` events) |
| `user_id` | STRING | No | User identifier (e.g., customer ID, email hash) if user is logged in |
| `value` | STRING | No | Additional data (e.g., URL for `link_open` or `deeplink_open`) |
| `device_model` | STRING | No | Device model (optional enhancement) |
| `os_version` | STRING | No | Operating system version (optional enhancement) |
| `app_version` | STRING | No | Application version (optional enhancement) |


### Sample Table Creation Query


```sql
-- Sample push_events table creation (customize for your needs)
CREATE TABLE IF NOT EXISTS mobile.push_events (
  time INTEGER,
  type STRING,
  td_campaign_id STRING,
  platform STRING,
  fcm_token STRING,
  user_id STRING,
  value STRING,
  device_model STRING,
  os_version STRING,
  app_version STRING
)
```

## Event Types

### 1. Delivery Event

Logged when a push notification is successfully delivered and displayed to the user.

**Event Type:** `delivery`

**Sample Event:**


```json
{
  "time": 1734134400,
  "type": "delivery",
  "td_campaign_id": "cmp_20251214_promo",
  "platform": "android",
  "user_id": "user_12345"
}
```

**When Tracked:**

- **Android**: When `onMessageReceived()` is called in `MyFirebaseMessagingService`
- **iOS (foreground)**: When `willPresent notification` is called in `UNUserNotificationCenterDelegate`
- **iOS (background)**: When `didReceiveRemoteNotification` is called for a notification with `content-available: 1`. This requires the app-side setup described in the [iOS Developer Guide](/products/marketing-cloud/engage-studio/channels/mobile-push/developer-guide-ios#enabling-delivery-and-dismiss-tracking-required-app-side-setup).


### 2. Open Event

Logged when a user taps on the notification to open the app.

**Event Type:** `open`

**Sample Event:**


```json
{
  "time": 1734134410,
  "type": "open",
  "td_campaign_id": "cmp_20251214_promo",
  "platform": "ios",
  "user_id": "user_12345"
}
```

**When Tracked:**

- **Android**: When notification is tapped and `MainActivity` receives `PushAction.OPEN` intent
- **iOS**: When `didReceive response` is called with `UNNotificationDefaultActionIdentifier`


### 3. Dismiss Event

Logged when a user dismisses (swipes away) the notification without opening it.

**Event Type:** `dismiss`

**Sample Event:**


```json
{
  "time": 1734134420,
  "type": "dismiss",
  "td_campaign_id": "cmp_20251214_promo",
  "platform": "android",
  "user_id": "user_12345"
}
```

**When Tracked:**

- **Android**: When `deleteIntent` is triggered in notification
- **iOS**: When `didReceive response` is called with `UNNotificationDismissActionIdentifier`


### 4. Link Open Event

Logged when a user taps on a web URL link from the notification.

**Event Type:** `link_open`

**Sample Event:**


```json
{
  "time": 1734134430,
  "type": "link_open",
  "td_campaign_id": "cmp_20251214_promo",
  "platform": "android",
  "user_id": "user_12345",
  "value": "https://example.com/promo?utm_campaign=promo"
}
```

**When Tracked:**

- **Android**: When action button with `PushAction.ACTION_LINK` is tapped
- **iOS**: When action button with identifier `ACTION_OPEN_LINK` is tapped


**Note:** The `value` field contains the full URL that was opened.

### 5. Deeplink Open Event

Logged when a user taps on an app deeplink from the notification.

**Event Type:** `deeplink_open`

**Sample Event:**


```json
{
  "time": 1734134440,
  "type": "deeplink_open",
  "td_campaign_id": "cmp_20251214_promo",
  "platform": "ios",
  "user_id": "user_12345",
  "value": "myapp://product/12345"
}
```

**When Tracked:**

- **Android**: When action button with `PushAction.ACTION_DEEPLINK` is tapped
- **iOS**: When action button with identifier `ACTION_OPEN_DEEPLINK` is tapped


**Note:** The `value` field contains the deeplink URI.

### 6. Token Register Event

Logged when the app registers or updates the FCM device token with Treasure Data.

**Event Type:** `token_register`

**Sample Event:**


```json
{
  "time": 1734134400,
  "type": "token_register",
  "fcm_token": "dQw4w9WgXcQ:APA91bF...",
  "platform": "android",
  "user_id": "user_12345",
  "app_version": "1.2.3",
  "os_version": "14",
  "device_model": "Pixel 7"
}
```

**When Tracked:**

- **Android**: When `onNewToken()` is called in `MyFirebaseMessagingService`
- **iOS**: When `didReceiveRegistrationToken` is called in `MessagingDelegate`
- Also triggered on app launch to ensure token is up-to-date


**Note:**

- For logged-in users, `user_id` should be included
- For anonymous users, `user_id` can be `null` — it will be associated later when the user logs in


## User ID Association

### Linking Tokens to Users

The `token_register` event is crucial for linking device tokens to user identities. Here's how the association works:

#### Scenario 1: User Already Logged In

When a user installs the app and is already logged in (or logs in immediately):


```json
{
  "type": "token_register",
  "fcm_token": "ABC123...",
  "user_id": "user_12345",
  "platform": "android"
}
```

The token is immediately associated with the user.

#### Scenario 2: Anonymous User (Not Logged In)

When a user installs the app but doesn't log in yet:


```json
{
  "type": "token_register",
  "fcm_token": "ABC123...",
  "user_id": null,
  "platform": "android"
}
```

The token is registered but not yet linked to a user.

#### Scenario 3: User Logs In Later

When the previously anonymous user logs in, send a new `token_register` event:


```json
{
  "type": "token_register",
  "fcm_token": "ABC123...",
  "user_id": "user_12345",
  "platform": "android"
}
```

Treasure Data can now link all previous events with this `fcm_token` to `user_12345`.

### Name Resolution Query

To resolve anonymous tokens to users, use this query:


```sql
WITH latest_tokens AS (
  SELECT
    fcm_token,
    user_id,
    time,
    ROW_NUMBER() OVER (PARTITION BY fcm_token ORDER BY time DESC) AS rn
  FROM mobile.push_events
  WHERE type = 'token_register'
    AND user_id IS NOT NULL
)
SELECT
  fcm_token,
  user_id
FROM latest_tokens
WHERE rn = 1
```

## Analytics Queries

### Campaign Performance Overview

Get delivery, open, and click rates for all campaigns:


```sql
SELECT
  td_campaign_id,
  COUNT(CASE WHEN type = 'delivery' THEN 1 END) AS deliveries,
  COUNT(CASE WHEN type = 'open' THEN 1 END) AS opens,
  COUNT(CASE WHEN type = 'link_open' OR type = 'deeplink_open' THEN 1 END) AS clicks,
  COUNT(CASE WHEN type = 'dismiss' THEN 1 END) AS dismissals,
  ROUND(100.0 * COUNT(CASE WHEN type = 'open' THEN 1 END) / NULLIF(COUNT(CASE WHEN type = 'delivery' THEN 1 END), 0), 2) AS open_rate_pct,
  ROUND(100.0 * COUNT(CASE WHEN type = 'link_open' OR type = 'deeplink_open' THEN 1 END) / NULLIF(COUNT(CASE WHEN type = 'delivery' THEN 1 END), 0), 2) AS click_rate_pct
FROM mobile.push_events
WHERE time >= CAST(strftime('%s', 'now', '-30 days') AS INTEGER)
GROUP BY td_campaign_id
ORDER BY deliveries DESC
```

### Platform Comparison

Compare performance between Android and iOS:


```sql
SELECT
  platform,
  COUNT(CASE WHEN type = 'delivery' THEN 1 END) AS deliveries,
  COUNT(CASE WHEN type = 'open' THEN 1 END) AS opens,
  ROUND(100.0 * COUNT(CASE WHEN type = 'open' THEN 1 END) / NULLIF(COUNT(CASE WHEN type = 'delivery' THEN 1 END), 0), 2) AS open_rate_pct
FROM mobile.push_events
WHERE td_campaign_id = 'cmp_20251214_promo'
GROUP BY platform
```

### Time-to-Open Analysis

Analyze how quickly users open notifications:


```sql
WITH delivery_times AS (
  SELECT
    td_campaign_id,
    user_id,
    fcm_token,
    time AS delivery_time
  FROM mobile.push_events
  WHERE type = 'delivery'
),
open_times AS (
  SELECT
    td_campaign_id,
    user_id,
    fcm_token,
    time AS open_time
  FROM mobile.push_events
  WHERE type = 'open'
)
SELECT
  CASE
    WHEN (o.open_time - d.delivery_time) < 60 THEN '< 1 minute'
    WHEN (o.open_time - d.delivery_time) < 300 THEN '1-5 minutes'
    WHEN (o.open_time - d.delivery_time) < 3600 THEN '5-60 minutes'
    WHEN (o.open_time - d.delivery_time) < 86400 THEN '1-24 hours'
    ELSE '> 24 hours'
  END AS time_bucket,
  COUNT(*) AS count
FROM delivery_times d
JOIN open_times o
  ON d.td_campaign_id = o.td_campaign_id
  AND d.user_id = o.user_id
  AND d.fcm_token = o.fcm_token
GROUP BY time_bucket
ORDER BY MIN(o.open_time - d.delivery_time)
```

### Most Clicked Links

Identify which links receive the most clicks:


```sql
SELECT
  td_campaign_id,
  value AS clicked_url,
  COUNT(*) AS click_count,
  COUNT(DISTINCT user_id) AS unique_users
FROM mobile.push_events
WHERE type IN ('link_open', 'deeplink_open')
  AND value IS NOT NULL
GROUP BY td_campaign_id, value
ORDER BY click_count DESC
LIMIT 20
```

### Daily Active Tokens

Track daily active device tokens:


```sql
SELECT
  DATE(time, 'unixepoch') AS event_date,
  platform,
  COUNT(DISTINCT fcm_token) AS active_tokens
FROM mobile.push_events
WHERE time >= CAST(strftime('%s', 'now', '-30 days') AS INTEGER)
  AND type IN ('delivery', 'open', 'link_open', 'deeplink_open')
GROUP BY event_date, platform
ORDER BY event_date DESC, platform
```

### User Engagement Funnel

Analyze the engagement funnel for a specific campaign:


```sql
WITH funnel_data AS (
  SELECT
    user_id,
    MAX(CASE WHEN type = 'delivery' THEN 1 ELSE 0 END) AS delivered,
    MAX(CASE WHEN type = 'open' THEN 1 ELSE 0 END) AS opened,
    MAX(CASE WHEN type IN ('link_open', 'deeplink_open') THEN 1 ELSE 0 END) AS clicked
  FROM mobile.push_events
  WHERE td_campaign_id = 'cmp_20251214_promo'
  GROUP BY user_id
)
SELECT
  SUM(delivered) AS total_delivered,
  SUM(opened) AS total_opened,
  SUM(clicked) AS total_clicked,
  ROUND(100.0 * SUM(opened) / NULLIF(SUM(delivered), 0), 2) AS open_rate_pct,
  ROUND(100.0 * SUM(clicked) / NULLIF(SUM(opened), 0), 2) AS click_through_rate_pct
FROM funnel_data
```

## Data Retention and Archival

### Partition Strategy

For large-scale deployments, consider partitioning the table by date:


```sql
-- Create monthly partitioned tables
CREATE TABLE mobile.push_events_202512 AS
SELECT * FROM mobile.push_events
WHERE time >= 1733011200 AND time < 1735689600
```

### Archive Old Events

Archive events older than 90 days to a separate table:


```sql
CREATE TABLE mobile.push_events_archive AS
SELECT * FROM mobile.push_events
WHERE time < CAST(strftime('%s', 'now', '-90 days') AS INTEGER)
```

Then delete from the main table:


```sql
DELETE FROM mobile.push_events
WHERE time < CAST(strftime('%s', 'now', '-90 days') AS INTEGER)
```

## Best Practices

### 1. Use Dedicated Table for Push Events

**Recommended:** Store push notification events in a separate `push_events` table.

**Reason:** Push events have a different schema from general app events (e.g., `event_app`), making it easier to query and analyze.

### 2. Include User ID When Available

Always include `user_id` in events when the user is logged in. This enables:

- User-level analytics
- Cross-device tracking
- Personalized campaign optimization


### 3. Batch Event Uploads

Upload events in batches (up to 500 events per request) to:

- Reduce network overhead
- Improve app performance
- Ensure reliable delivery even with poor network conditions


### 4. Implement Retry Logic

If event upload fails:

- Keep events in local queue
- Retry with exponential backoff
- Limit maximum retry attempts (e.g., 3-5 times)


### 5. Monitor Event Quality

Regularly check for:

- Missing required fields (`td_campaign_id`, `platform`, `type`)
- Duplicate events (same `td_campaign_id` + `user_id` + `fcm_token` + `type` + `time`)
- Abnormal event patterns (e.g., opens without deliveries)


### 6. Add Custom Fields as Needed

Enhance the schema with additional fields relevant to your business:

- `device_model`: Track performance by device type
- `os_version`: Identify OS-specific issues
- `app_version`: Correlate events with app releases
- `location`: Geo-based analytics (ensure privacy compliance)


## Security and Privacy

### Data Protection

- **Do not log PII**: Avoid storing email addresses, phone numbers, or other personally identifiable information in events
- **Hash user IDs**: Consider hashing user IDs before sending to Treasure Data
- **Use write-only keys**: Mobile apps should use write-only API keys, never master keys
- **Encrypt in transit**: All data uploads use HTTPS


### GDPR/Privacy Compliance

If your app serves users in privacy-regulated regions:

- Obtain user consent before tracking events
- Provide opt-out mechanisms
- Implement data deletion workflows
- Document data retention policies


## Related Documentation

- [Mobile Push Setup](/products/marketing-cloud/engage-studio/channels/mobile-push/mobile-push-setup) - Configure Firebase and Treasure Data integration
- [Android Developer Guide](/products/marketing-cloud/engage-studio/channels/mobile-push/developer-guide-android) - Implement push notifications on Android
- [iOS Developer Guide](/products/marketing-cloud/engage-studio/channels/mobile-push/developer-guide-ios) - Implement push notifications on iOS
- [Campaign Creation](/products/marketing-cloud/engage-studio/channels/mobile-push) - Create and send Mobile Push campaigns