Skip to content

login() does not deduplicate push subscriptions on the same device — causes duplicate notification delivery #1964

Description

@invivek26

Summary

Calling OneSignal.login(externalId) on every app launch does not retire or deduplicate stale push subscriptions on the same physical device. Over time, multiple enabled push subscriptions accumulate under the same external_id for a single device, causing every notification sent via include_aliases: { external_id: [...] } to be delivered N times (once per enabled subscription).

Environment

  • react-native-onesignal: 5.4.5 (also reproduced behavior on data created by earlier SDK versions)
  • React Native: 0.81.4
  • Expo: 54
  • Platforms: iOS 26.x (iPhone 16) and Android 16 (Pixel)

Reproduction

  1. Install app, call OneSignal.initialize() then OneSignal.login(userId) on launch
  2. Use app normally — subscription created correctly
  3. Update iOS (e.g. 26.4 → 26.5), or reinstall/clear data on Android
  4. Launch app again — login(userId) is called with the same external ID
  5. Check subscriptions via GET /apps/{app_id}/users/by/external_id/{userId}

Expected: One enabled push subscription per device per platform type.

Actual: Multiple enabled push subscriptions accumulate for the same device_model and platform type. Examples observed:

  • iOS user: 2 enabled iOSPush subscriptions on the same iPhone model (SDK 050501, different OS versions 26.4.2 and 26.5.1), both with valid APNs tokens
  • Android user: 7 AndroidPush subscriptions on the same Pixel device (6 disabled with the same stale FCM token, 1 enabled with current token), plus 2 additional enabled subscriptions on old devices that were never cleaned up

Impact — Duplicate Notification Delivery

When sending a push notification targeted at a single external_id:

{
  "include_aliases": { "external_id": ["<userId>"] },
  "headings": { "en": "Title" },
  "contents": { "en": "Body" },
  "target_channel": "push"
}

The OneSignal delivery stats show successful: 2 for the iOS user (single device, single API call). The user physically receives the notification twice on the same phone.

Root Cause Analysis

login() transfers the current device's subscription to the identified user, but does not:

  1. Check if the user already has an enabled push subscription for the same device_model + platform type
  2. Retire/disable the previous subscription on the same device
  3. Deduplicate before delivery

There is also no server-side guard — no API parameter like "deliver once per device" exists. collapse_id only collapses the notification tray; both deliveries still happen and both count as successful.

Related Issues

This is a long-standing class of bugs across SDKs:

  • Android SDK #2110 — App kill + reopen creates duplicate subscription (fixed in PR #2099)
  • Android SDK #2143 — 20-subscription cap deadlocks login forever (still open, 5+ reporters, acknowledged as bug by @jkasten2)
  • Android SDK #2514 — Restricted external ID corrupts device, 12-15 orphan subs (still open)
  • Android SDK PR #2618 — login/logout race fixed May 2026
  • iOS SDK PR #1669 — iOS prewarm creates duplicate subscriptions (fixed SDK 5.5.2)
  • React Native #1829 — logout/login doesn't clear old external ID (still open)

In #2143, a OneSignal engineer stated: "We are looking into removing older push subscriptions automatically so this extra integration step won't be needed in the future." — has there been progress on this?

Feature Request

  1. login() should deduplicate subscriptions per device — when a user logs in and a new push subscription is created, any existing enabled push subscription for the same device_model + platform type under the same external_id should be automatically disabled or deleted.

  2. Server-side dedup option — an API parameter (e.g., deduplicate_per_device: true) on the Create Notification endpoint that ensures at most one delivery per physical device per external_id, regardless of how many subscription records exist.

  3. Automatic stale subscription cleanup — subscriptions with enabled: false that share an FCM/APNs token with a newer enabled subscription on the same user should be garbage-collected automatically.

Current Workaround

We are building a server-side cleanup job that periodically:

  1. Fetches each user's subscriptions via the View User API
  2. Identifies duplicates (multiple enabled subscriptions of the same type on the same device_model)
  3. Deletes stale ones via DELETE /apps/{app_id}/subscriptions/{subscription_id}

This works but should not be necessary — the SDK and platform should handle this automatically.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions