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

# Duplicated notifications

> Troubleshoot duplicate push notifications caused by SDK conflicts, server-side retries, multi-app environments, and platform-specific bugs.

Duplicate notifications occur when the same device receives the same message content more than once. This guide covers the most common causes and how to resolve them.

If the same user sees the notification on multiple devices (phone, tablet, desktop), that is expected behavior based on your targeting configuration (segments, external IDs, etc.). For duplicate in-app messages, see the [in-app message troubleshooting](./in-app-message-troubleshooting#duplicated-in-app-messages) guide instead.

<Info>
  Apple acknowledged a bug in iOS 17 that caused duplicates. This was fixed in iOS 17.3. [Read more](https://forums.developer.apple.com/forums/thread/747044).
</Info>

## Start here

Pick the section that best matches your situation:

* Your server retries failed API calls or your backend pipeline may double-send → [Same message sent multiple times](#same-message-sent-multiple-times)
* Your app uses Firebase or another push SDK alongside OneSignal → [Third-party notification handlers](#third-party-notification-handlers)
* Your Android app customizes notifications in code (service extension or foreground listener) → [Android notification handlers](#android-notification-handlers)
* Your iOS app implements its own `UNUserNotificationCenterDelegate` → [iOS foreground handler](#ios-foreground-handler)
* You only see duplicates on dev builds or with multiple installs → [Multiple app instances](#multiple-app-instances)

## Same message sent multiple times

The most common cause is sending the same notification payload more than once through the OneSignal API. Common reasons:

* Your server retries requests without checking if the first succeeded.
* Logic in your backend pipeline sends the same notification twice.
* You're migrating to OneSignal but still sending from a previous provider. Avoid sending from both systems at the same time.

<Card title="Idempotent API requests" icon="rotate" href="/reference/idempotent-notification-requests">
  Prevent duplicate messages by reusing idempotency keys on retries.
</Card>

## Third-party notification handlers

Duplicates can occur when other code in your app processes OneSignal's push payload and displays its own notification in addition to OneSignal's. This includes:

* Other remote push SDKs (for example, Firebase Cloud Messaging) that receive the same payload.
* Custom message handlers such as a `FirebaseMessagingService` on Android or a `UNUserNotificationCenterDelegate` on iOS that read the incoming payload and build a local notification from it.

OneSignal's SDK filters out OneSignal payloads automatically when it is the only handler. Third-party code does not know to filter them out unless you tell it to.

### Identifying OneSignal notifications

Every OneSignal notification includes a `custom` object with an `i` key containing the OneSignal notification UUID:

```json JSON theme={null}
{
  "custom": {
    "i": "the-notification-id"
  }
}
```

For the full payload reference, see [Custom OneSignal payload structure](./osnotification-payload#custom-onesignal-payload-structure).

### Filtering OneSignal notifications from third-party handlers

In any non-OneSignal handler, check for the `custom.i` key and return early when it is present. OneSignal then handles its own payloads while your other code continues to process its own.

<Tabs>
  <Tab title="Android">
    In a custom `FirebaseMessagingService` or other remote message handler:

    ```kotlin Kotlin theme={null}
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        val customJson = remoteMessage.data["custom"]
        if (customJson != null) {
            try {
                val custom = JSONObject(customJson)
                if (custom.has("i")) {
                    // OneSignal notification — let the OneSignal SDK handle it
                    return
                }
            } catch (e: JSONException) {
                // Not a OneSignal payload, fall through
            }
        }

        // Handle your own notification here
    }
    ```
  </Tab>

  <Tab title="iOS">
    In your `UNUserNotificationCenterDelegate` or a custom notification handler:

    ```swift Swift theme={null}
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        let userInfo = notification.request.content.userInfo

        if let custom = userInfo["custom"] as? [String: Any], custom["i"] != nil {
            // OneSignal notification — let the OneSignal SDK handle it
            completionHandler([])
            return
        }

        // Handle your own notification here
    }
    ```
  </Tab>
</Tabs>

<Tip>
  If you control the payload structure sent from your other provider, add a distinct marker key (for example, `my_app_notification: true`) and filter in both directions — only handle notifications containing your marker in your own code, and let OneSignal handle notifications containing the `custom.i` key.
</Tip>

## Android notification handlers

OneSignal exposes two Android callbacks that let you intercept and customize a notification before it displays:

* `onNotificationReceived` in a [notification service extension](./service-extensions) — runs for every push, including when the app is in the background.
* `onWillDisplay` in a [foreground lifecycle listener](./mobile-sdk-reference#addforegroundlifecyclelistener-push) — runs only when the app is in the foreground.

Duplicates happen when these callbacks display the notification in addition to OneSignal's automatic display.

### The `preventDefault()` and `display()` rule

OneSignal displays each notification automatically unless you suppress it with `event.preventDefault()`. Two common mistakes cause duplicates:

* Calling `event.getNotification().display()` without first calling `event.preventDefault()` — shows the notification twice.
* Posting a separate notification with [`NotificationManagerCompat.notify()`](https://developer.android.com/reference/androidx/core/app/NotificationManagerCompat) while OneSignal also displays the original.

Only one display path should be active — either OneSignal's `display()` or your own `NotificationManagerCompat.notify()`, never both.

<CodeGroup>
  ```kotlin Kotlin theme={null}
  override fun onNotificationReceived(event: INotificationReceivedEvent) {
      event.preventDefault()

      val notification = event.notification
      notification.setExtender { builder ->
          builder.setColor(0xFF0000FF.toInt())
      }
      notification.display()
  }
  ```

  ```java Java theme={null}
  @Override
  public void onNotificationReceived(INotificationReceivedEvent event) {
      event.preventDefault();

      IDisplayableMutableNotification notification = event.getNotification();
      notification.setExtender(builder ->
          builder.setColor(0xFF0000FF)
      );
      notification.display();
  }
  ```
</CodeGroup>

The same rule applies inside `onWillDisplay` in a foreground lifecycle listener:

```kotlin Kotlin theme={null}
OneSignal.Notifications.addForegroundLifecycleListener(object : INotificationLifecycleListener {
    override fun onWillDisplay(event: INotificationWillDisplayEvent) {
        event.preventDefault()

        // Render your own UI or call event.notification.display() to show the OneSignal notification
    }
})
```

<Warning>
  If you call `preventDefault()` but never call `display()`, the notification is silently dropped and the user does not see it. Every code path should either call `display()` once or intentionally suppress the notification.
</Warning>

<Warning>
  If you use both a notification service extension and a foreground lifecycle listener, make sure only one handler displays the notification for a given app state. Calling `display()` from both produces duplicates.
</Warning>

### Async work inside callbacks

When you do background work (network calls, database lookups) before displaying, call `preventDefault()` synchronously in the callback and call `display()` only after the async work completes. Returning from the callback without `preventDefault()` lets OneSignal display the notification, and a later `display()` call produces a duplicate.

## iOS foreground handler

OneSignal sets itself as the `UNUserNotificationCenterDelegate` during SDK initialization. If your app also implements foreground notification handling, the notification can display twice — once from OneSignal and once from your code.

Common patterns that cause duplicates:

* Implementing your own `UNUserNotificationCenterDelegate` without forwarding calls to OneSignal, then calling `completionHandler([.banner, .sound])` and showing a custom in-app alert from the same method.
* Scheduling a local notification (`UNUserNotificationCenter.current().add(...)`) in response to an incoming push that OneSignal is also displaying.

To control foreground display without causing duplicates, use OneSignal's [foreground lifecycle listener](./mobile-sdk-reference#addforegroundlifecyclelistener-push) and call `event.preventDefault()` when you want to render the notification yourself:

```swift Swift theme={null}
OneSignal.Notifications.addForegroundLifecycleListener(self)

func onWillDisplay(event: OSNotificationWillDisplayEvent) {
    event.preventDefault()

    // Render your own UI or call event.notification.display() to show the OneSignal notification
}
```

## Multiple app instances

<Tabs>
  <Tab title="Android">
    Duplicates can happen when you have both production and development versions of your app installed. Each app has a unique package name and receives its own push token.

    Long-press the notification to confirm which app instance sent it.
  </Tab>

  <Tab title="iOS">
    If two device records in OneSignal share the same push token, the device may receive two notifications. This can happen when the same token is imported or registered multiple times. OneSignal includes checks to prevent this, but edge cases may bypass them.
  </Tab>

  <Tab title="Web">
    Web push duplicates are caused by subscribing to multiple origins using the same OneSignal App ID. An [origin](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) is the combination of scheme, host, and port that the browser uses to scope push subscriptions.

    For example, a user who subscribes to `https://example.com` and `https://sub.example.com` counts as two subscriptions, and a send using the same App ID reaches both.

    To fix:

    1. [Reset browser data and push permissions](./troubleshooting-web-push) per origin.
    2. If many users are affected, create a new OneSignal App and use the new App ID in your site's init code.
    3. Have users revisit the site and resubscribe.

    Subscribing on multiple browsers or browser profiles also leads to multiple notifications.
  </Tab>
</Tabs>

## Diagnostic tips

To debug duplicates faster, collect and send the following to OneSignal support:

* OneSignal Subscription ID of the device that received the duplicates
* OneSignal Message ID or a link to the message in the dashboard
* List of other libraries or plugins in your app
* [Debug log reproducing the issue](./capturing-a-debug-log)
* Detailed reproduction steps

## FAQ

### What happens if I have two notification SDKs in my app?

Both SDKs may independently process and display the same notification. OneSignal filters its own notifications automatically, but other SDKs do not. Filter OneSignal payloads out of your other SDK's handlers by checking for the `custom.i` key. See [Third-party notification handlers](#third-party-notification-handlers) for code examples.

### How do I send push from a previous provider and OneSignal at the same time?

You can transition gradually, but avoid sending the same message from both providers.

* **Android**: Remove old SDK notification handling code when integrating OneSignal and releasing the app. As users update, they stop receiving push from the old provider.
* **iOS**: You can continue sending from the old provider temporarily while users update. Once fully transitioned, send from OneSignal only to avoid duplicates.

### How do I prevent multiple notifications for rapidly-changing updates?

Use a [`collapse_id`](/reference/push-notification#body-parameters) in the [Create notification](/reference/push-notification) API to replace previous notifications instead of stacking them. When multiple notifications share the same `collapse_id`, each new one replaces the previous notification in the tray. This is useful for stock price updates, live scores, delivery ETAs, and similar frequent updates.

## Related pages

<Columns cols={2}>
  <Card title="Mobile SDK troubleshooting" icon="mobile" href="./mobile-troubleshooting">
    Resolve common issues with push notification delivery on iOS and Android.
  </Card>

  <Card title="Web push troubleshooting" icon="browser" href="./troubleshooting-web-push">
    Debug web push subscription, service worker, and delivery issues.
  </Card>

  <Card title="In-app message troubleshooting" icon="message" href="./in-app-message-troubleshooting">
    Fix issues with in-app message display, triggers, and duplicates.
  </Card>

  <Card title="Mobile service extensions" icon="gear" href="./service-extensions">
    Intercept and customize push notifications before display on iOS and Android.
  </Card>

  <Card title="Notifications not shown (web)" icon="bell-slash" href="./notifications-not-shown-web-push">
    Troubleshoot web push notifications that are sent but not displayed.
  </Card>
</Columns>
