Skip to main content
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 guide instead.
Apple acknowledged a bug in iOS 17 that caused duplicates. This was fixed in iOS 17.3. Read more.

Start here

Pick the section that best matches your situation:

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.

Idempotent API requests

Prevent duplicate messages by reusing idempotency keys on retries.

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
{
  "custom": {
    "i": "the-notification-id"
  }
}
For the full payload reference, see 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.
In a custom FirebaseMessagingService or other remote message handler:
Kotlin
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
}
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.

Android notification handlers

OneSignal exposes two Android callbacks that let you intercept and customize a notification before it displays: 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() while OneSignal also displays the original.
Only one display path should be active — either OneSignal’s display() or your own NotificationManagerCompat.notify(), never both.
override fun onNotificationReceived(event: INotificationReceivedEvent) {
    event.preventDefault()

    val notification = event.notification
    notification.setExtender { builder ->
        builder.setColor(0xFF0000FF.toInt())
    }
    notification.display()
}
The same rule applies inside onWillDisplay in a foreground lifecycle listener:
Kotlin
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
    }
})
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.
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.

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 and call event.preventDefault() when you want to render the notification yourself:
Swift
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

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.

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
  • 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 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 in the Create 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.

Mobile SDK troubleshooting

Resolve common issues with push notification delivery on iOS and Android.

Web push troubleshooting

Debug web push subscription, service worker, and delivery issues.

In-app message troubleshooting

Fix issues with in-app message display, triggers, and duplicates.

Mobile service extensions

Intercept and customize push notifications before display on iOS and Android.

Notifications not shown (web)

Troubleshoot web push notifications that are sent but not displayed.