# iOS message delivery tracking

There is an [API method](/developer/api-reference/device-api#messagedeliveryevent) in Pushwoosh that tracks the delivery of push notifications. iOS apps do not support this method out of the box, because push notifications on iOS are handled by the OS, not by the Pushwoosh SDK. You can add delivery tracking by adding a Notification Service Extension to your project. This page shows how to implement message delivery tracking for iOS apps.

<Aside>
Requires Pushwoosh iOS SDK 7.x, which supports iOS 13.0 and later.
</Aside>

<Aside type="note">
Since Pushwoosh iOS SDK 7.1.0 the recommended integration is the drop-in `PushwooshNotificationServiceExtension` base class shown below. It sends the message delivery event, sets the badge, downloads the media attachment, and handles the mandatory `serviceExtensionTimeWillExpire` fallback for you. The older `PWNotificationExtensionManager` API still works but is deprecated — see [Legacy integration](#legacy-integration).
</Aside>

## Add Notification Service Extension

1. In Xcode, select **File** > **New** > **Target...**

2. Select **Notification Service Extension** and press **Next.**

<img src="/ios-push-notifications-ios-message-delivery-tracking-1.webp" alt="Xcode target template picker with Notification Service Extension selected"/>

3. Enter the product name and press **Finish.**

<Aside type="caution">
Do not select **Activate** on the dialog that is shown after pressing **Finish**.
</Aside>

4. Press **Cancel** on the **Activate scheme** prompt.

<img
  src="/ios-push-notifications-ios-message-delivery-tracking-2.webp"
  alt="Activate scheme prompt with Cancel highlighted"
  style={{ display: "block", margin: "0 auto", maxWidth: "40%", height: "auto" }}
  width="400"
/>

By canceling, you keep Xcode debugging your app instead of the extension you just created. If you activated it by accident, you can switch back to debugging your app within Xcode.

## Dependencies for the Notification Service Extension (CocoaPods only)

If you use Swift Package Manager to manage dependencies, you can skip this step, as the dependencies are added automatically.

Open your `Podfile` and add the dependency for the target:

```ruby title="Podfile"
target 'NotificationServiceExtension' do
  use_frameworks!
  pod 'PushwooshXCFramework'
end
```

Run the following commands in the terminal to install the dependencies:

```shell
rm -rf Podfile.lock
pod deintegrate
pod setup
pod repo update
pod install
```

## Add code for tracking message delivery events

Make your extension a subclass of `PushwooshNotificationServiceExtension`. An empty subclass is enough: Pushwoosh sends the message delivery event, sets the badge, downloads the media attachment, and handles the timeout fallback automatically.

Replace the generated contents of your **NotificationService** file:

<Tabs>
<TabItem label="Swift">

```swift
import UserNotifications
import PushwooshFramework

class NotificationService: PushwooshNotificationServiceExtension {}
```

</TabItem>

<TabItem label="Objective-C">

```objective-c
#import <PushwooshFramework/PushwooshNotificationServiceExtension.h>

@interface NotificationService : PushwooshNotificationServiceExtension

@end

@implementation NotificationService

@end
```

</TabItem>
</Tabs>

<Aside type="tip">
If you do not need any custom code, you can skip the source file entirely and point the extension's Info.plist `NSExtensionPrincipalClass` at `PushwooshNotificationServiceExtension` directly.
</Aside>

### App ID

Since 7.1.0 the extension inherits `Pushwoosh_APPID` (and other `Pushwoosh_*` keys) from the host app's Info.plist, so you no longer need to duplicate it in the extension. Add `Pushwoosh_APPID` to the extension Info.plist only when you want to override the host value:

```xml title="NotificationService/Info.plist"
<key>Pushwoosh_APPID</key>
<string>XXXXX-XXXXX</string>
```

<Aside type="note">
On Pushwoosh iOS SDK versions earlier than 7.1.0 the extension does not inherit configuration from the host app. On those versions you must add `Pushwoosh_APPID` to the extension Info.plist.
</Aside>

### App Group (badge and reverse proxy)

An App Group shared between the app and the extension is needed to sync the badge count and to read reverse-proxy settings that the host app stores.

1. Add the **App Groups** capability to the extension target and enable the same group there as in the host app. This is required — without the shared container the badge count and reverse-proxy settings cannot be synced.

2. Provide the App Group name. Like `Pushwoosh_APPID`, the extension inherits `PW_APP_GROUPS_NAME` from the host app's Info.plist since 7.1.0, so if you already set it there for badges you do not need to add it to the extension. Set it in the extension Info.plist only to override the host value, or provide it programmatically by overriding `pushwooshAppGroupsName`.

```xml title="App Info.plist"
<key>PW_APP_GROUPS_NAME</key>
<string>group.com.example.app</string>
```

<Aside type="caution">
If the host app uses a reverse proxy (`Pushwoosh_ALLOW_REVERSE_PROXY`), the extension needs this App Group to read the proxy URL the app stored there. Without it, the delivery event is held back rather than sent directly, bypassing the proxy.
</Aside>

## Customize the notification (optional)

The base class exposes a few override points, from least to most control. Pushwoosh still runs the delivery event, badge, attachment, and timeout fallback in every case.

Set the App Group programmatically instead of using the Info.plist key:

```swift
override func pushwooshAppGroupsName() -> String? {
    "group.com.example.app"
}
```

Run asynchronous preparation before Pushwoosh processes the push — for example, prefetching Push Stories media — without overriding the standard `didReceive`. Call `completion` exactly once, on the main thread:

```swift
override func pushwooshPrepare(for request: UNNotificationRequest,
                              completion: @escaping () -> Void) {
    // async work here
    completion()
}
```

Modify the content before it is shown by overriding `didReceive`. Call `super` with your own content handler, mutate the content inside it, then forward it to the original handler:

```swift
override func didReceive(_ request: UNNotificationRequest,
                         withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    super.didReceive(request) { content in
        let mutable = (content.mutableCopy() as? UNMutableNotificationContent) ?? content
        // customize `mutable` here
        contentHandler(mutable)
    }
}
```

## Legacy integration

<Aside type="caution">
`PWNotificationExtensionManager` is deprecated since 7.1.0. Use it only if you cannot subclass `PushwooshNotificationServiceExtension` — for example, an extension that already extends another SDK's base class, or a cross-platform wrapper (React Native, Flutter, Unity). New integrations should use the base class above.
</Aside>

This low-level API drives the same processing (delivery event, badge, attachment) from a plain `UNNotificationServiceExtension`:

<Tabs>
<TabItem label="Swift">

```swift
import UserNotifications
import PushwooshFramework

class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest,
                             withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        PWNotificationExtensionManager.sharedManager()
            .handleNotificationRequest(request, contentHandler: contentHandler)
    }
}
```

</TabItem>

<TabItem label="Objective-C">

```objective-c
#import "PWNotificationExtensionManager.h"

@interface NotificationService : UNNotificationServiceExtension

@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
    [[PWNotificationExtensionManager sharedManager] handleNotificationRequest:request
                                                              contentHandler:contentHandler];
}

@end
```

</TabItem>
</Tabs>

## Share your feedback with us

Your feedback helps us create a better experience, so we would love to hear from you if you have any issues during the SDK integration process. If you face any difficulties, please do not hesitate to share your thoughts with us [via this form](https://docs.google.com/forms/d/e/1FAIpQLSd_0b8jwn-V_JmoPLIxIFYbHACCQhrzidOZV3ELywoQPXRSxw/viewform).