# Analytics & Reporting

There are two ways to get data on the performance of your ContextPush integration:

1. We will prepare reports on measured open rates for all running campaigns and share them with you on a regular basis.
2. You can integrate with our Webhook solution to track performance on your end, see next section.

## Webhooks

To get started with ContextPush webhooks reach out to us under <support@contextsdk.com> and provide us with a URL where we should push the webhook events to.

For the initial setup you only need to share a secret with us that can be used to verify that the webhooks are coming from our service.

ContextPush supports two types of webhooks:

* **Message Status Webhooks** — Notify you about delivery status changes for individual messages (e.g., delivered, opened, cancelled).
* **Device Transition Webhooks** — Notify you when a device's [classification](https://docs.contextsdk.com/context-push/device-classification) changes (e.g., from `unknown` to `eligible`).

### Common Headers

All webhooks include the following headers:

| Header              | Description                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-CTX-Secret`      | Your shared secret. Verify this before processing the request.                                                                                                      |
| `X-CTX-Event-Type`  | The event type — `message_status` for delivery updates, `device_transition` for classification changes.                                                             |
| `X-Idempotency-Key` | A unique key for the entire webhook HTTP request. This is distinct from the `idempotency_key` field inside each payload item, which identifies an individual event. |

## Message Status Webhook

### Format

The webhook request will be sent to you in the following format:

```
POST /your_webhook_endpoint
Host: your.host
X-CTX-Secret: your_shared_secret
X-CTX-Event-Type: message_status
X-Idempotency-Key: idempotency_key
Content-Type: application/json

[
  {
    "device_token": "push_token",
    "user_id": "your_user_id",
    "campaign_id": "your_campaign_id",
    "idempotency_key": "idempotency_key",
    "delivery_status": "opened",
    "delivery_method": "context_aware",
    "planned_delivery_method": "context_aware",
    "timestamp": "2025-02-07T15:20:40.599Z",
    "content": {
      "title": "James, it's time for your English course! 👑",
      "body": "Start your English course now!"
    }
  },
  …
]
```

Each request can contain multiple entries, the maximum being 100.

To illustrate the format here is the TypeScript type definition:

```ts
type DeliveryStatus = 'failed' | 'cancelled' | 'opened' | 'delivered' | 'pending';
type DeliveryMethod = 'context_aware' | 'traditional' | 'cancelled';
type PlannedDeliveryMethod = 'context_aware' | 'traditional';

// A single message status event
export type MessageStatusEvent = {
  device_token: string | undefined; // Will be undefined when the delivery_status is cancelled.
  user_id: string;
  campaign_id: string;
  idempotency_key: string;
  delivery_status: DeliveryStatus;
  delivery_method: DeliveryMethod;
  planned_delivery_method: PlannedDeliveryMethod;
  timestamp: string; // ISO 8601 Timestamp
  content: MessageContent; // Copied 1:1 from what you supplied when scheduling the message.
};

// The webhook request body is an array of events
export type MessageStatusWebhookPayload = MessageStatusEvent[];
```

Your secret will be sent in the `X-CTX-Secret` header and should be verified by you before processing the request.

### Attaching Extra Information

Since the full content when scheduling the notification is echoed back to you you can use the `content.userInfo` property to attach any data you might require during later processing. Simply set it when scheduling the notification.

### Explanation of Delivery Status

* `pending` - This notification has not been sent yet, the user has not seen this notification.
* `delivered` - The APNS servers confirmed that they successfully accepted the request to send the notification, and it most likely was shown to the user. ContextPush currently does not implement confirmed deliveries using a `UNNotificationServiceExtension`.
* `opened` - The user has clicked on this notification.
* `cancelled` - A request was sent to cancel this notification from being delivered, this status guarantees that the user will not have seen this notification.
* `failed` - The notification could not be sent because the `device_token` was no longer valid.

### Explanation of Delivery Methods

* `context_aware` - This notification was delivered using ContextPush as a context-aware notification, in a good moment.
* `traditional` - This notification was delivered as a traditional push notification, without considering the users real-world context.

### Identifying a Single Message

Depending on if you care about delivery per user, or per device to uniquely define a single message the combination of: `idempotency_key`, `campaign_id`, `user_id` and optionally `device_token` will be unique. Consider that after 30 days messages are deleted from our system so the key may be reused at that time.

### Other Implementation Notes

Due to the nature of distributed systems we can only guarantee at-least-once delivery of the final state change, you might not receive all intermediary state changes, and we cannot guarantee the order e.g. you might receive a `delivered` event after an `opened` event for the same message, and even the same device.

ContextPush will retry delivery of failed webhooks up to 10 times over a 2h window, after that they are dropped.

## Device Transition Webhook

Device transition webhooks notify your backend whenever a device's [classification](https://docs.contextsdk.com/context-push/device-classification) changes. This allows you to keep a user attribute (e.g., `bot_status`) in sync so you can route messages through the optimal delivery path.

{% hint style="info" %}
For background on what device classification is and how to integrate with it, see [Device Classification](https://docs.contextsdk.com/context-push/device-classification).
{% endhint %}

### Format

The webhook request will be sent to you in the following format:

```
POST /your_webhook_endpoint
Host: your.host
X-CTX-Secret: your_shared_secret
X-CTX-Event-Type: device_transition
X-Idempotency-Key: idempotency_key
Content-Type: application/json

[
  {
    "device_token": "push_token",
    "user_id": "your_user_id",
    "old_bot_status": "unknown",
    "new_bot_status": "eligible",
    "timestamp": "2025-02-07T15:20:40.599Z",
    "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
  },
  …
]
```

Each request can contain multiple entries, the maximum being 50.

To illustrate the format here is the TypeScript type definition:

```ts
type BotStatus = 'eligible' | 'ineligible' | 'unknown';

// A single device transition event
export type DeviceTransitionEvent = {
  device_token: string;
  user_id: string;
  old_bot_status: BotStatus;
  new_bot_status: BotStatus;
  timestamp: string;  // ISO 8601 Timestamp
  idempotency_key: string;
};

// The webhook request body is an array of events
export type DeviceTransitionWebhookPayload = DeviceTransitionEvent[];
```

### Field Reference

BOT in `old_bot_status` / `new_bot_status` stands for **Background Operation Time** — the iOS capability required for context-aware delivery.

| Field             | Description                                                                                              |
| ----------------- | -------------------------------------------------------------------------------------------------------- |
| `device_token`    | The APNs device token identifying the device.                                                            |
| `user_id`         | The same user ID you use when scheduling messages through ContextPush (set via `ContextPush.setUserId`). |
| `old_bot_status`  | The device's previous BOT classification (`eligible`, `ineligible`, or `unknown`).                       |
| `new_bot_status`  | The device's new BOT classification.                                                                     |
| `timestamp`       | When the transition was detected (ISO 8601).                                                             |
| `idempotency_key` | A unique key for this transition event, use it to deduplicate on your end.                               |

### Explanation of Classification Values

* `eligible` — Background Operation Time is likely available on this device, enabling context-aware delivery.
* `ineligible` — Background Operation Time is likely not available on this device. Messages should be sent through your existing push infrastructure.
* `unknown` — Not enough data has been collected yet to classify this device. This is the initial state for all new devices.

### Implementation Notes

* A webhook is only sent when a device **changes** classification. You will not receive webhooks for devices that remain in the same category.
* Due to the nature of distributed systems we can only guarantee at-least-once delivery. Use the `idempotency_key` on each transition item to deduplicate events on your end.
* **Respect the `timestamp` field when applying updates.** Events may arrive out of order. Store the `timestamp` alongside the current BOT status on your end and only apply an incoming transition if its `timestamp` is newer than the one you have stored.
* ContextPush will retry delivery of failed webhooks up to 10 times over a 2h window, after that they are dropped.
