Common Push Notifications Bugs and How to Catch Them

Common Push Notifications Bugs and How to Catch Them

May 26, 2026 · 17 min read · Common Issues

Common Push Notifications Bugs and How to Catch Them

Push notifications are a fragile integration point between backend services, platform SDKs, and the host operating system. A missing permission, a malformed payload, or an OS‑specific quirk can turn a useful alert into a silent failure that frustrates users and skews engagement metrics. This guide walks through twelve recurring bug patterns, shows exactly how each manifests to users, gives reproducible steps, outlines detection tactics (both manual and automated), and provides concrete fixes and preventive measures.

Understanding Push Notification Failures

Before diving into specific bugs, it helps to map the notification lifecycle:

  1. Registration – the app requests a token from FCM/APNs and forwards it to your server.
  2. Payload creation – the backend builds a JSON (FCM) or dictionary (APNs) payload, often adding custom keys for deep‑link handling.
  3. Transport – the message travels via FCM/APNs to the device; network issues, token expiry, or throttling can intervene.
  4. Receipt – the OS wakes the app (or runs a service extension) and delivers the payload.
  5. Presentation – the notification is posted to the shade/lock screen; the UI may be customized (actions, images, grouping).
  6. Interaction – the user taps the notification; the app routes to a screen or performs a background task.

A defect can appear at any stage. The sections below isolate the most common failure points, explain why they arise, and give a repeatable way to catch them before release.

Common Push Notifications Bugs and How to Catch Them: Overview

Bug IDPrimary SymptomTypical Root CauseDetection LeverFix Category
P1No permission dialog shown on first launchManifest missing POST_NOTIFICATIONS (Android 13+) or UNUserNotificationCenter not calledLaunch‑time UI test, log check for registerForRemoteNotificationsManifest / code addition
P2Device receives no notification despite backend 200Payload missing notification key or malformed JSON; FCM rejects silentlyBackend log inspection, FCM delivery receipt, device logcat for FirebaseMessagingPayload schema validation
P3Same notification appears two or more timesServer retries without deduplication; client does not collapse identical collapse_keyCount notifications in shade after a burst, check FCM collapse_key handlingServer‑side idempotency, client collapse key
P4Notification heads‑up or lock‑screen entry missingPriority set to low; channel importance mis‑configured; iOS interruptionLevel set to passiveVerify notification appears on lock screen after device lock, check channel importanceAdjust priority/importance, correct iOS interruption level
P5Tap opens wrong screen or crashesIntent/extras mismatched; deep‑link URL not validated; service extension returns nil UNNotificationResponseUI test tapping notification, assert target activity/fragment; crashlytics for EXC_BAD_ACCESSValidate intent extras, deep‑link parser, extension safety
P6Notifications stack incorrectly on Android 12+Group key missing or mismatched; setGroupAlertBehavior not usedSend two notifications with same group, observe shade groupingSet setGroup and setGroupAlertBehavior correctly
P7Delivery delayed >10 min despite immediate sendBackground fetch throttled; APNs silent push limited; FCM collapsible message throttledMeasure time between send and onMessageReceived; check device battery optimization whitelistUse high‑priority FCM, avoid silent pushes, request exemptions for background work
P8Service extension crashes, causing fallback to raw payloadUnhandled exception in didReceiveNotificationRequest, exceeding time limitCrashlytics for extension, console log for EXTENSION_BOOMWrap extension code in try/catch, keep work < seconds, offload heavy tasks
P9Token rotation leads to messages sent to stale tokenApp does not refresh token after onNewToken/didRefreshRegistrationToken; server caches old tokenCompare server‑side token DB with fresh device token after app reinstall or OS updateSubscribe to token refresh callbacks, invalidate server cache on mismatch
P10Garbled characters or missing localizationPayload encoded as UTF‑8 but server sends ISO‑8859‑1; iOS falls back to system localeInspect raw payload via proxy (e.g., Charles), check displayed string for mojibakeEnforce UTF‑8 everywhere, add Content‑Type: application/json; charset=utf-8 header
P11Notifications throttled by OS after bursts >5/minAndroid’s NotificationManagerPolicy or iOS’s interruptionLevel + timeSensitive flags cause silent dropsSend burst, monitor shade for missing entries, check adb shell cmd notification statsRespect OS limits, combine related updates, use setTimeoutAfter for iOS
P12Accessibility services ignore notification (TalkBack/VoiceOver)Missing contentDescription or accessibilityTitle; notification lacks setCategory for announcementRun TalkBack, verify announcement; use Accessibility Scanner for missing descriptorsAdd contentDescription, set appropriate notification category, test with accessibility services

Each bug is explored in depth below.

Bug Pattern 1: Missing Permission Prompt

Why it happens

Android 13 introduced the runtime permission POST_NOTIFICATIONS. If the manifest omits or the app never calls NotificationManagerCompat.from(context).areNotificationsEnabled(), the system silently discards all notification posts. On iOS, forgetting to call UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) yields the same result.

User‑visible symptom

The user never sees a permission dialog; subsequent pushes appear to “do nothing.” In analytics, opt‑in rates drop to zero.

Reproduction steps

  1. Install a clean build on a device/API 33+ (Android) or iOS 15+.
  2. Launch the app; do not trigger any permission request manually.
  3. From your backend, send a test push.
  4. Observe the shade/lock screen – no notification appears.
  5. Check logcat for W/NotificationService: Not posting notification, user has blocked notifications (Android) or console for [UNUserNotificationCenter] Authorization status: notDetermined (iOS).

Detection tactics

Fix and prevention

Bug Pattern 2: Silent Failures Due to Incorrect Payload

Why it happens

FCM expects a top‑level notification object for display messages and/or a data object for silent payloads. If you send only a data payload without setting priority: "high" on Android, the system may treat it as low‑priority and defer delivery. APNs requires the aps dictionary; missing it results in the payload being dropped silently.

User‑visible symptom

Backend logs show HTTP 200 from FCM/APNs, yet the device never receives a notification. No error surfaces in Firebase Console or APNs feedback.

Reproduction steps

  1. Use curl to send a malformed payload:
  2. 
       curl -X POST https://fcm.googleapis.com/v1/projects/my-proj/messages:send \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            -H "Content-Type: application/json; charset=utf-8" \
            -d '{
                  "message": {
                    "token": "<DEVICE_TOKEN>",
                    "data": {
                      "score": "5x"
                    }
                  }
                }'
    
  3. On the device, run adb logcat | grep FirebaseMessaging (Android) or Console.app filter MyApp (iOS).
  4. No onMessageReceived callback fires; no notification appears.

Detection tactics

Fail the CI build if the payload does not conform.

Fix and prevention

Bug Pattern 3: Duplicate Notifications

Why it happens

Duplicates arise when the backend retries a failed HTTP request without deduplication, or when the client receives the same FCM message with identical collapse_key but fails to collapse it because the key is missing or null. On iOS, repeated silent pushes can cause multiple UNUserNotificationCenterDelegate callbacks if the app does not ignore duplicates based on a payload UUID.

User‑visible symptom

The shade shows two or more identical entries; tapping one may trigger the same action twice, leading to duplicate state changes (e.g., two items added to a cart).

Reproduction steps

  1. Configure your push sender to send the same message twice within 2 seconds.
  2. Ensure the payload contains "collapse_key": "order_update_123" (Android) or apns-collapse-id header (iOS).
  3. Watch the notification shade; you should see a single entry.
  4. Remove the collapse_key or set it to an empty string and repeat – you will now see two entries.

Detection tactics

Fix and prevention

Bug Pattern 4: Notification Not Appearing on Lock Screen

Why it happens

Android channels with IMPORTANCE_LOW or IMPORTANCE_NONE never show heads‑up or lock‑screen entries. iOS treats notifications with interruptionLevel .passive as silent unless the user has enabled “Show on Lock Screen” in Settings → Notifications → Your App.

User‑visible symptom

The user receives a notification only after unlocking the device; they miss time‑sensitive alerts (e.g., OTP).

Reproduction steps

  1. Send a push with channel importance set to IMPORTANCE_LOW.
  2. Lock the device immediately after sending.
  3. Unlock and check the shade – notification appears, but it was not on the lock screen.
  4. Repeat with IMPORTANCE_HIGH and verify lock‑screen visibility.

Detection tactics

Fix and prevention

Bug Pattern 5: Tap Action Leads to Wrong Screen or Crash

Why it happens

The notification’s tap intent may contain stale deep‑link parameters, or the PendingIntent may be constructed with FLAG_IMMUTABLE (Android 12+) causing the extras to be stripped. On iOS, the UNNotificationResponse may have a userInfo dictionary missing the expected deep‑link key, leading to a forced‑unwrap crash.

User‑visible symptom

Tapping the notification opens the home screen, a generic splash, or crashes the app with an exception like NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference.

Reproduction steps

  1. Send a notification with payload:
  2. 
       {
         "notification": { "title": "Promo", "body": "20% off" },
         "data": { "screen": "promo", "itemId": "42" }
       }
    
  3. Ensure the PendingIntent is built with getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE).
  4. Tap the notification; observe that the app launches MainActivity without reading itemId.
  5. Change the flag to FLAG_MUTABLE (if targeting SDK 31+) and repeat – the extras survive.

Detection tactics

Fix and prevention

Bug Pattern 6: Notification Grouping Issues on Android 12+

Why it happens

Android 12 introduced stricter grouping rules: notifications without a setGroup key are considered ungrouped, while those with a mismatched groupKey appear as separate stacks. Additionally, setGroupAlertBehavior controls whether children generate heads‑up alerts.

User‑visible symptom

A burst of chat messages appears as many individual entries instead of a single collapsed group, causing shade clutter and missed summaries.

Reproduction steps

  1. Send three messages in quick succession with the same groupKey: "chat_convo_42".
  2. Omit setGroupAlertBehavior(GROUP_ALERT_SUMMARY) on the first posting.
  3. Observe the shade: three separate notifications, no summary.
  4. Add the group alert behavior and repeat – you now see a single stacked notification with a summary line.

Detection tactics

Fix and prevention

Bug Pattern 7: Background Fetch Limits Causing Delayed Delivery

Why it happens

Both platforms throttle background work to preserve battery. FCM treats low‑priority messages as “delayable” if the app is in a restricted background state (e.g., battery optimization, Doze mode). APNs may silently discard pushes marked content-available if the app has exceeded its background execution quota.

User‑visible symptom

A user expects an instant update (e.g., live score) but sees it only after opening the app or after a significant delay (several minutes).

Reproduction steps

  1. Enable battery optimization for the app (Settings → Apps → YourApp → Battery → Optimize battery usage).
  2. Send a low‑priority FCM message ("priority": "normal").
  3. Record the time between send and onMessageReceived.
  4. Disable optimization and repeat – delivery time drops from >30 s to <2 s.

Detection tactics

Fix and prevention

Bug Pattern 8: iOS Notification Service Extension Crashes

Why it happens

The Notification Service Extension has a strict runtime limit (≈ seconds) and limited memory. Throwing an uncaught exception or performing synchronous network calls can cause the system to terminate the extension and fall back to delivering the raw payload, which may lack custom UI modifications (e.g., image attachment, modified title).

User‑visible symptom

Users see a notification with the original title/body instead of the expected enriched version (e.g., no inline image, missing action buttons). In some cases, the app crashes shortly after tapping because the extension’s failure left shared state inconsistent.

Reproduction steps

  1. In the extension’s didReceiveNotificationRequest, force a crash:
  2. 
       override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
           let badArray = [Int]()
           _ = badArray[10]   // triggers EXC_BAD_INSTRUCTION
           self.contentHandler(request.content)
       }
    
  3. Send a push with a mutable-content: 1 flag and an attachment URL.
  4. Observe that the notification appears without the attachment and that the console logs EXTENSION_BOOM.

Detection tactics

Fix and prevention

Bug Pattern 9: Firebase Cloud Messaging Token Rotation Issues

Why it happens

FCM tokens can change when: the app is restored on a new device, the user reinstalls the app, clears app data, or the system refreshes security tokens. If your server continues to push to the stale token, FCM returns a NotRegistered error, which you might ignore, leading to silent message loss.

User‑visible symptom

A subset of users (often those who recently updated or reinstalled) stops receiving pushes altogether, while others receive them normally.

Reproduction steps

  1. Obtain a fresh FCM token on a test device.
  2. Send a push – verify receipt.
  3. Simulate token rotation by calling FirebaseMessaging.getInstance().deleteToken() (or clearing app data).
  4. Obtain the new token; note that it differs from the old one.
  5. Continue sending pushes using the old token; observe that FCM returns NotRegistered in the response body.

Detection tactics

Fix and prevention

Bug Pattern 10: Localization and Encoding Problems

Why it happens

Push payloads often travel through multiple services (API gateway, queue, worker). If any service defaults to ISO‑8859‑1 or fails to declare UTF‑8, multilingual characters (e.g., emojis, accented letters) become garbled or appear as replacement symbols.

User‑visible symptom

Users see “???” or garbled text in the notification title/body, reducing trust and causing confusion (especially for OTPs containing non‑ASCII digits).

Reproduction steps

  1. Craft a push with payload:
  2. 
       { "title": "¡Oferta!", "body": "🎉 20% de descuento" }
    
  3. Send it via a middleware that incorrectly sets Content-Type: text/plain.
  4. On the device, inspect the notification; the title shows ¡Oferta! and the body shows garbled emojis.

Detection tactics

Fix and prevention

Bug Pattern 11: Over‑aggressive Rate Limiting by OS

Why it happens

Both Android and iOS apply rate limiting to prevent notification spam. Android may collapse or silently drop notifications if the same app posts more than a threshold (e.g., 5 notifications per second) unless they use distinct notificationIds or are marked as ongoing. iOS groups notifications and may suppress further alerts if the user has not interacted with recent ones.

User‑visible symptom

During a burst of events (e.g., live match updates), the user sees only the first few notifications; later updates appear to be “missing.”

Reproduction steps

  1. Send 10 notifications in rapid succession (interval < 200 ms) with the same notificationId.
  2. Observe the shade: only the first 1‑2 appear; the rest are absent.
  3. Change each notification to use a unique notificationId (e.g., timestamp) and repeat – all appear.

Detection tactics

Fix and prevention

Bug Pattern 12: Accessibility Failures (TalkBack/VoiceOver)

Why it happens

Accessibility services rely on the notification’s contentDescription (Android) or accessibilityLabel (iOS) to announce the event. If you only set the visible title/body and neglect these fields, TalkBack may read the package name or say “Notification, no description.”

User‑visible symptom

Users with visual impairments miss critical information (e.g., two‑factor authentication codes) because the screen reader either says nothing useful or reads irrelevant metadata.

Reproduction steps

  1. Send a notification with title “Your code is 123456”.
  2. Enable TalkBack (Settings → Accessibility → TalkBack).
  3. Lock the device, then unlock to trigger the announcement.
  4. Listen – TalkBack may say “Notification from com.example.app” without reading the code.

Detection tactics

Fix and prevention

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free