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

# Activity Tracking

> Forward push delivery and engagement events from your app to WorldVisa. Supported for FCM, APNs, Expo, OneSignal, Pusher Beams, Pushpad, and Push Webhook.

Your app captures push delivery and engagement events and forwards them to WorldVisa. WorldVisa records each event on the matching message in the **Activity Feed**.

## Supported providers

The **Push Activity Tracking** toggle is available on these integrations:

* [Firebase Cloud Messaging (FCM)](https://docs.worldvisagroup.com/developers/integrations/push/fcm.md)
* [APNs](https://docs.worldvisagroup.com/developers/integrations/push/apns.md)
* [Expo](https://docs.worldvisagroup.com/developers/integrations/push/expo-push.md)
* [OneSignal](https://docs.worldvisagroup.com/developers/integrations/push/onesignal.md)
* [Pusher Beams](https://docs.worldvisagroup.com/developers/integrations/push/pusher-beams.md)
* [Pushpad](https://docs.worldvisagroup.com/developers/integrations/push/pushpad.md)
* [Push Webhook](https://docs.worldvisagroup.com/developers/integrations/push/push-webhook.md)

AppIO has no **Push Activity Tracking** toggle, and WorldVisa rejects activity events for an AppIO integration. AppIO delivers into the IO app, where you cannot run code, and its message API has no custom data field for the WorldVisa message id.

## How it works

The process involves a four-step data flow from your subscriber's device to WorldVisa's servers:

1. **Client application listens**: Your application listens for push notification interactions. For example, the user opens a notification.
2. **Event sent to your backend:** When an event occurs, your application sends a payload containing the event details to an endpoint on your own server.
3. **Backend forwards to WorldVisa:** Your server receives this data and uses the WorldVisa SDK to securely forward the event to WorldVisa's API.
4. **Event appears in WorldVisa:** WorldVisa processes the event and displays it in the **Activity Feed**, alongside events from your other channels.

## Step 1: Enable push activity tracking in WorldVisa

Enable push activity tracking in your WorldVisa dashboard and get the necessary credentials.

1. Log in to the WorldVisa dashboard.
2. Navigate to the **Integration Store** page, and then select your push provider.
3. Enable the **Push Activity Tracking** toggle.

   ![Enable push activity tracking toggle in the WorldVisa integration store](https://mintcdn.com/wv-enterprise/l3bkwl1N_3mOn9aW/images/developers/channels-and-providers/push/activity-tracking/enable-activity-tracking.png?fit=max\&auto=format\&n=l3bkwl1N_3mOn9aW\&q=85\&s=2a3a3555eb0d70198b60e856d625a475)
4. Once enabled, your unique **Environment ID** and **Integration ID** are displayed. Copy and save both of these; you will need them for your backend code.
5. Click **Save Changes**.

## Step 2: Listen for push events in your application

When a push notification is delivered or the subscriber interacts with it, capture that event and forward it to your backend. The listener depends on the provider.

Send these fields:

* `eventType`: One of `delivered`, `opened`, `clicked`, `dismissed`, or `failed`. WorldVisa ignores any other value.
* `eventId`: The WorldVisa message id for that notification.

| `eventType` | What WorldVisa records |
| - | - |
| `delivered` | A delivery time on the message |
| `opened` | The message as seen |
| `clicked` | The message as seen, plus a click |
| `dismissed` | The message as seen, plus a read |
| `failed` | The message as failed |

WorldVisa puts that message id on the notification before it is sent. Read it from the field for your provider and send it back as `eventId`.

| Provider | Where to read the message id |
| - | - |
| Expo | `data.__nvMessageId` |
| FCM | `data.__nvMessageId` |
| APNs | `__nvMessageId` beside `aps` |
| OneSignal | `additionalData.__nvMessageId` |
| Pusher Beams | Android: FCM `data.__nvMessageId`. Web: `notification.data.pusher.customerPayload.data.__nvMessageId`. iOS: `data.__nvMessageId`, with `data` beside `aps`. |
| Push Webhook | `messageId` on the webhook body. The same value is `payload.__nvMessageId`. |
| Pushpad | `custom_data`. This value is the message id string. |

On a standard FCM notification, `data` contains `__nvMessageId` and any `data` keys from your FCM override. The rest of the trigger payload is included only on a data-only message (`overrides.providers.fcm.type` set to `data`).

An APNs `rawPayload` override replaces the notification body. `__nvMessageId` is on the device only when that object contains it. A trigger-time `rawPayload` does not, because WorldVisa assigns the message id at send time.

> [!WARNING]
>
> Pushpad passes `custom_data` only to [action button](https://pushpad.xyz/docs/action_buttons) callbacks. A tap on the notification body does not include the message id, so that tap cannot be forwarded. Add an action button and read the id from the callback argument. Chrome is the only browser that shows action buttons, and it shows at most two. Keep the `custom_data` value WorldVisa sets. Replacing it removes the message id.

> [tab] expo
>
> ```jsx theme={null}
> // Listen for notification interactions
> Notifications.addNotificationResponseReceivedListener(async (response) => {
>   const eventData = {
>     eventType: "clicked",
>     eventId: response.notification.request.content.data?.__nvMessageId,
>     timestamp: new Date().toISOString(),
>     actionIdentifier: response.actionIdentifier,
>     content: {
>       title: response.notification.request.content.title,
>       body: response.notification.request.content.body,
>       data: response.notification.request.content.data,
>     },
>     // Optional device context
>     deviceId: Constants.sessionId,
>     platform: Platform.OS,
>   };
>
>   await fetch("https://your-api.com/api/notifications/events", {
>     method: "POST",
>     headers: { "Content-Type": "application/json" },
>     body: JSON.stringify(eventData),
>   });
> });
> ```

> [tab] fcm
>
> ```javascript theme={null}
> import messaging from '@react-native-firebase/messaging';
> import { Platform } from 'react-native';
>
> const trackNotificationOpen = async (remoteMessage) => {
>   const eventData = {
>     eventType: "opened",
>     eventId: remoteMessage?.data?.__nvMessageId,
>     timestamp: new Date().toISOString(),
>     content: {
>       title: remoteMessage?.notification?.title,
>       body: remoteMessage?.notification?.body,
>     },
>     deviceId: await messaging().getToken(),
>     platform: Platform.OS,
>   };
>
>   try {
>     await fetch("https://your-api.com/api/notifications/events", {
>       method: "POST",
>       headers: {
>         "Content-Type": "application/json"
>       },
>       body: JSON.stringify(eventData),
>     });
>   } catch (err) {
>     console.log("Failed to track notification event", err);
>   }
> };
>
> // App opened from background
> messaging().onNotificationOpenedApp(trackNotificationOpen);
>
> // App opened from quit state
> messaging()
> .getInitialNotification()
> .then((remoteMessage) => {
>   if (remoteMessage) {
>     trackNotificationOpen(remoteMessage);
>   }
> });
> ```

> [tab] onesignal
>
> ```js theme={null}
> OneSignal.Notifications.addEventListener("click", async (event) => {
>   const eventData = {
>     eventType: "clicked",
>     eventId: event.notification.additionalData?.__nvMessageId,
>     timestamp: new Date().toISOString(),
>     content: {
>       title: event.notification.title,
>       body: event.notification.body,
>     },
>   };
>
>   await fetch("https://your-api.com/api/notifications/events", {
>     method: "POST",
>     headers: { "Content-Type": "application/json" },
>     body: JSON.stringify(eventData),
>   });
> });
> ```

> [tab] pushpad
>
> ```js theme={null}
> // service-worker.js
> // Bind this function to a Pushpad action button whose Action is "trackClick".
> // customData is the WorldVisa message id from custom_data.
> self.notificationActions = {
>   trackClick: function (customData) {
>     return fetch("https://your-api.com/api/notifications/events", {
>       method: "POST",
>       headers: { "Content-Type": "application/json" },
>       body: JSON.stringify({
>         eventType: "clicked",
>         eventId: customData,
>       }),
>     });
>   },
> };
> ```

Add the Pushpad button on the notification, for example with a [provider content override](https://docs.worldvisagroup.com/developers/integrations/push#every-other-push-provider-is-a-raw-passthrough):

```json theme={null}
{
  "actions": [
    { "title": "View", "action": "trackClick" }
  ]
}
```

## Step 3: Forward events to WorldVisa from your backend

Create an endpoint on your backend that receives the event data from your application and uses the WorldVisa SDK to forward it to WorldVisa.

```bash theme={null}
curl -L -X POST "${WV_API_URL}/v1/activity/track" \
-H 'Content-Type: application/json' \
-H "Authorization: ApiKey ${WV_API_KEY}" \
-d '{
  "environmentId": "<WV_ENVIRONMENT_ID>",
  "integrationId": "<WV_INTEGRATION_ID>",
  "requestBody": {
    "eventType": "opened",
    "eventId": "<__nvMessageId>"
  }
}'
```

> [!NOTE]
>
> Both `Integration ID` and `Environment ID` can be found in the push provider integration page after enabling Push Activity Tracking.

Once these steps are completed, your application will send push notification engagement data to WorldVisa. This gives you a complete, unified view of your notification performance in the Activity Feed.


## Related topics

- [Expo Push Push Integration with WorldVisa](https://docs.worldvisagroup.com/developers/integrations/push/expo-push.md)
- [Push Webhook Push Integration with WorldVisa](https://docs.worldvisagroup.com/developers/integrations/push/push-webhook.md)
- [OneSignal Push Integration with WorldVisa](https://docs.worldvisagroup.com/developers/integrations/push/onesignal.md)
- [APNS Push Integration with WorldVisa](https://docs.worldvisagroup.com/developers/integrations/push/apns.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.