Skip to content
Bugwise
Menu
Features & Flows

Push Notifications

How push notifications reach a phone, how permissions, app states and deep links change what users see, what usually breaks, and how testers trigger and test pushes on iOS, Android and the web.

40 min intermediate Mobile

It’s 8 pm, you’re hungry, and your phone buzzes: “Marco picked up your order. Arriving in 18 min.” You tap it, and the app opens straight on the order with a live map. That moment feels effortless, but behind it are your app, your company’s server, Apple or Google, the phone’s operating system, a permission the user gave months ago, and a deep link. Any of them can break.

And when push breaks, users notice fast. The notification arrives twice. It arrives at 3 am. It says “Your order from {{restaurant}}”. It keeps coming after you turned it off, so people uninstall the app. This lesson shows you how push notificationsPush notificationA message an app sends to the device that appears even when the app is closed.Open in glossary → work, where they fail, and how to test them without waiting for a real order.

How it works

A push notification is a message your company’s server sends to an app on a phone, even when the app isn’t open. The server doesn’t contact the phone directly. It goes through a delivery service run by the platform owner:

  • APNs (Apple Push Notification service) for iPhone and iPad.
  • FCM (Firebase Cloud Messaging) from Google for Android (and it can forward to APNs for iOS too).
  1. 1Your app

    Asks the user for permission, then asks the operating system for a device token.

    token: a1f3…9c
  2. 2APNs (Apple) / FCM (Google)

    Issues the token. It is the "postal address" of this app on this device.

  3. 3Your company’s server

    Stores the token next to the user account. When the order changes, it sends a message to APNs or FCM.

    { to: a1f3…9c, title: "On the way" }
  4. 4APNs / FCM

    Delivers the message to the device (now, or later if the phone is offline).

  5. 5The device

    The operating system shows the banner, sound and badge. The app decides what happens on tap.

Your server never contacts the phone directly. It hands the message to Apple or Google, and they deliver it.
  1. The app asks for permission. Without it, the phone won’t show anything (more on this below).
  2. The app registers and gets a device token. A long random string from APNs or FCM that identifies this app on this device. Think of it as a postal address.
  3. The app sends the token to your server. The server stores it next to the user account, e.g. “Anna has an iPhone with token a1f3…9c”.
  4. Something happens. The restaurant confirms, the driver picks up the food, a sale starts.
  5. The server sends a message to APNs or FCM with the token, the text and extra data (like which order it’s about).
  6. APNs or FCM delivers it to the phone. If the phone is offline, they keep it for a while and deliver later.
  7. The operating system shows it. Banner, sound, badge, lock screen. When the user taps it, the app opens and should take them to the right screen.

Push, local and in-app notifications

Not every notification is a push. Testers should know which kind they’re looking at, because they break in different ways:

KindWho creates itExample
Push (remote)Your server, via APNs/FCM“Your order is on the way”
LocalThe app itself, scheduled on the phone, no internet needed“Time for your 7 am workout”, a timer that finished
In-app messageThe app’s own UI, only while the app is openA banner or toastToastA short, temporary message that appears briefly and disappears on its own, like 'Message sent'.Open in glossary → inside the app: “Marco is 2 minutes away”

A common bug is mixing them up: the server sends a push and the app schedules a local reminder for the same thing, so the user gets both.

Permission: asking at the right moment

For every permission type, the system dialogs and what happens after each answer, see App Permissions.

Users decide whether an app may notify them, and they can change their mind any time in system Settings. This is one of the app permissionsApp permissionsThe user's approval for an app to use things like the camera, location, contacts or notifications.Open in glossary → that every tester should know well.

iOS

  • The app shows Apple’s system dialog: “QuickBite” Would Like to Send You Notifications, with Allow and Don’t Allow.
  • iOS shows this dialog only once per install. After a “Don’t Allow”, the app can never show it again. The only way back is Settings › Notifications › QuickBite, so a good app explains that and offers a button to open Settings.
  • Timing matters. Asking on the very first screen, before the user understands the app, gets a lot of “Don’t Allow”. Good apps ask in context: right after the user places their first order (“Want to know when your food is on the way?”). See Onboarding for this “permission priming” pattern.
  • Provisional (quiet) notifications (iOS 12 and later): an app can skip the dialog and deliver notifications quietly to Notification Center only (no banner, no sound). Each one asks the user “Keep” or “Turn off”. Test that these really arrive quietly.

Android

  • Android 13 and newer need the runtime permission POST_NOTIFICATIONS: the app shows a system dialog with Allow and Don’t allow. If the user denies it twice, Android stops showing the dialog, and only system Settings can turn notifications on.
  • Android 12 and older have no dialog: notifications are on by default, and users turn them off in Settings. So test both an old and a new Android version.
  • Upgrades are tricky: existing apps that already sent notifications usually keep them after the phone updates to Android 13 (the permission is pre-granted), while apps that still target Android 12L or older get the dialog automatically when they create their first notification channel. Test what existing users see after the update.

Anatomy of a notification

iOS banner

On the way

now

Marco picked up your Pasta Roma order. Arriving in 18 min.

QuickBite

Long-press to see the image and actions

3QuickBite

Android (expanded)

QuickBite· Order updates2m

On the way

Marco picked up your Pasta Roma order.

Marco, 1.2 km away
Track orderCall driver
  1. 1App icon and name. Shows who sent it. Android uses a small monochrome icon in the status bar too.
  2. 2Title. Short and specific. Gets cut off first on small screens and in other languages.
  3. 3Body. The details. Check placeholders are filled in and long text is truncated nicely.
  4. 4Image (rich media). Optional picture. Must not block the notification if it fails to download.
  5. 5Actions. Buttons like "Track" or "Call driver". Each must do exactly what it says, even when the app is closed.
  6. 6Badge. The red count on the app icon. It should go down when the user reads the updates.
  7. 7Timestamp and grouping. Several messages from one order or chat should stack together, newest first.
  8. 8Channel (Android). Every notification belongs to a channel such as "Order updates" with its own importance and sound.

A few platform details that affect testing:

  • Android channels. Since Android 8, every notification belongs to a channel (like “Order updates” or “Offers”) and users can turn each channel on or off, or change its sound. The app sets each channel’s importance once: high pops up as a heads-up banner with sound, default makes a sound, low is silent. After that, the user owns the channel, and the app can’t change it in an update. A promo channel created with high importance by mistake stays noisy for every existing user.
  • iOS interruption levels. Notifications can be passive (silent), active (normal), time-sensitive (may break through a Focus mode) or critical (always plays a sound; needs special approval from Apple, e.g. for health alerts). A marketing push marked time-sensitive is a bug.
  • Grouping. Several messages about one order or one chat should stack together. Test five updates in a row: are they grouped and in the right order?
  • Badge. The red number on the app icon. On iOS the server sends the number in the message, and the app is responsible for clearing it when the user has seen the updates.

What happens in each app state

The same push behaves differently depending on the state of the appApp lifecycleThe states an app moves through: not running, foreground, background, suspended and terminated.Open in glossary →. This is where many push bugs live, so always test all three:

App stateWhat the user should seeWhat to check
Foreground (open, on screen)Usually no system banner. The app decides: show an in-app banner and refresh the screenThe order status updates live; nothing is silently ignored
Background (opened earlier, now hidden)A normal system banner, sound and badgeTap opens the right screen; data is fresh when the app returns
Killed (swiped away, or never opened since restart)A normal system bannerTap starts the app from scratch (a cold startCold startLaunching an app whose process isn't running, the slowest kind of launch, compared with a warm or hot start.Open in glossary →) and still opens the right screen

Tapping “Your order is on the way” should open that order, not the Home screen. The push carries a deep linkDeep linkA link that opens a specific screen inside an app instead of just its home screen.Open in glossary → or an ID (like order_id: 4821) that the app uses to navigate.

The classic bug: it works when the app is in the background, but from a killed state the app starts, shows the splash screen, and lands on Home. That’s because the code path for a cold start is different. Other things to try: tapping while logged out, tapping an old notification for an order that’s already delivered, and tapping when the app is open on another screen. The lesson Background and foreground explains these app states in depth.

The tricky details

How testers trigger pushes

You can’t wait for a real driver to pick up a real order. Ask your developers which of these your team uses:

  1. A staging admin panel or “push console”. Many teams build an internal page on the stagingStagingA pre-release environment that mirrors production as closely as possible.Open in glossary → server: pick a user, pick a message type, press Send. This is the most realistic option because it uses the real server code and templates.
  2. Firebase console. In Firebase: Messaging › New campaign › Notifications, write a title and text, then Send test message and paste the device’s FCM registration token (developers can show it in a debug menu or log it to logcatLogcatAndroid's system log, read with adb logcat, where apps write errors and crash details.Open in glossary →).
  3. iOS Simulator. Save a payload as a .apns file and run xcrun simctl push booted com.quickbite.app order.apns, or drag the file onto the Simulator window (for drag and drop, the file must also contain a "Simulator Target Bundle" key with the app’s bundle ID). Great for checking text, deep links and app states. Since Xcode 14 (macOS 13, Apple silicon or T2 Macs), the Simulator can also receive real remote pushes.
  4. Apple’s Push Notifications Console. A web tool in the Apple Developer account where you paste a device token and send a test push to a real iPhone.
  5. A real deviceReal deviceA physical phone or tablet used for testing, as opposed to an emulator or simulator.Open in glossary →, end to end. Before release, always test real pushes on real iPhones and Android phones, through the real server. Simulators can’t tell you about delivery delays, battery savers or a manufacturer’s aggressive background limits.

Here’s what a simple iOS payload looks like. Even if you never write one, it helps to recognize the fields in a bug report:

{
  "aps": {
    "alert": { "title": "On the way", "body": "Marco picked up your order." },
    "badge": 3,
    "sound": "default",
    "thread-id": "order-4821"
  },
  "order_id": "4821"
}

alert is the text, badge the number on the icon, thread-id groups notifications together, and order_id is custom data the app uses to open the right screen.

Example requirements

Criteria 3, 4, 6 and 7 are the ones teams most often forget to test, because they need a specific app state, a second device or a different time zone.

What usually goes wrong

#BugWhat you seeTypical severity
1Wrong screen on tapTapping a push from a killed app opens Home, not the orderMedium–High
2DuplicatesThe same notification twice (or once per reinstall)Medium
3Opt-out ignoredOffers keep coming after turning them off in the appHigh (trust, legal)
4Foreground push lostApp is open: no banner, the screen shows an old statusMedium
5Text problemsRaw {{restaurant}}, wrong language, title cut offMedium
6Badge never clearsRed “7” on the icon even after reading everythingLow–Medium
7Wrong timePromo arrives at 3 am because the server used its own time zoneMedium–High
8Permission asked too earlyDialog on first launch, user taps Don’t Allow, no way backHigh (business)
9Notifications after logoutNext user sees the previous user’s orders on the lock screenHigh (privacy)
10Private data on the lock screenFull bank balance or a health result in the previewHigh (privacy)

Find the bug

QuickBite’s order notifications have six bugs. You control the phone (app state, permission, Do Not Disturb, language, time zone) and a fake server console that sends pushes, just like a staging admin panel. Try this:

  • Send an update while the app is open on the order screen.
  • Go to the Home screen, send updates, tap them.
  • Force quit the app, send an update, tap it. Then send another one.
  • Turn things off in the app’s notification settings, then send that type.
  • Switch the language to Deutsch and send each event.
  • Watch the badge on the app icon, and the console log.

🐞 Bug hunt: QuickBite delivery notifications

This demo has 6 hidden bugs. Explore it like a tester, then check your findings.

Found0/6
9:41

QuickBite

Your order

Near you

Sushi Go · 20 min
Burger Lab · 25 min
Green Bowl · 15 min

Device

App state: Foreground (open)

Allow notifications (iOS Settings)
Do Not Disturb

Server console (staging)

Order #4821: Placed

Server time 20:30 UTC · user’s time 20:30

Anna’s settings: orders on · promos on

Device tokens (1): a1f3…9c

  • Send a push to see what happens…

From the spec

  • Tapping an order update opens that order.
  • In the open app, updates show an in-app banner and refresh the screen.
  • Users choose which types they get. Offers are never sent 21:00–08:00 their time.
  • The badge shows unread updates.
  1. 1

    Hint: Force quit the app, send "Picked up", then tap the notification.

  2. 2

    Hint: Force quit, open the app again, go to the Home screen and send any update. Check "Device tokens" in the console.

  3. 3

    Hint: In the app, open settings and turn off "Offers & promotions". Go to the Home screen and send the promo.

  4. 4

    Hint: Keep the app open on the order screen and send "Driver arriving".

  5. 5

    Hint: Switch the language to Deutsch, go to the Home screen and send "Order confirmed".

  6. 6

    Hint: Receive a few pushes, open the app and the order, then go back to the Home screen.

Test scenarios

Functional

  • Each event (confirmed, picked up, arriving, delivered) sends exactly one push with the right text.
  • Tapping each push opens the right screen in foreground, background and killed states.
  • With the app open, an in-app banner appears and the screen refreshes live.
  • In-app settings: turning a type off stops it, turning it on brings it back. Check the server really changed (a second device, or ask a developer).
  • The badge count matches unread updates and clears when they’re seen.
  • Notification actions (“Track order”, “Call driver”) do what they say, also from a killed app.

Negative

  • Permission denied: no notifications, no crash, settings screen explains how to turn them on.
  • Permission denied, then turned on later in system Settings: notifications start working without reinstalling.
  • Logged out: no more pushes for that account. Log in as another user: you get only their pushes.
  • The order was canceled or deleted: tapping an old push shows a friendly message, not a crash or blank screen.
  • Phone offline when the push is sent: when it comes back online, the notification arrives (once) or is skipped if it’s out of date.

Edge cases

  • Reinstall the app: still exactly one notification per event (no duplicate tokens).
  • Two devices with the same account: both get the push; reading it on one doesn’t break the other.
  • Many pushes quickly (five status changes in a few seconds): grouped, in the right order, no duplicates.
  • Time zones and quiet hours: change the phone’s time zone; offers respect the user’s local time. Try the night of a daylight saving time change.
  • Do Not Disturb / Focus on: notifications arrive silently and are still there afterwards.
  • Low Power Mode and Android battery savers: pushes still arrive (maybe delayed).
  • App update and OS update: permission and in-app settings are kept.
  • Long text and other languages: German title, a restaurant name with emoji or accents (Café Zürich), right-to-left languages.

UI & usability

  • Text is short, specific and useful: “Marco is 2 minutes away”, not “Order update”.
  • The right icon, app name and image; the image failing to load doesn’t block the notification.
  • Notifications are grouped per order or chat.
  • Sounds and vibration fit the importance (no loud sound for marketing).
  • Permission is asked in context, with an explanation first.

Security & privacy

  • No sensitive data in the preview text: no full card numbers, passwords, one-time codes for other services, health results. Check the lock screen with Show Previews: When Unlocked too.
  • Deep links from a push can’t open screens the user isn’t allowed to see (e.g. another user’s order).
  • After logout or account deletion, the device token is removed on the server.

Accessibility

Example test cases

TC-PUSH-001Tap push from killed app opens the order

Preconditions

  • Notifications allowed
  • Active order #4821
  • App force quit

Steps

  1. Send "Picked up" for order #4821 from the staging console
  2. Wait for the notification
  3. Tap it

Expected result

The app starts and opens Order #4821 with status "On the way".

TC-PUSH-002Foreground push shows in-app banner and updates the screen

Preconditions

  • App open on Order #4821 with status "On the way"

Steps

  1. Send "Driver arriving" from the staging console
  2. Watch the screen without touching it

Expected result

An in-app banner "Driver arriving" appears and the status changes to "Arriving" without a manual refresh.

TC-PUSH-003Turning off offers stops promo pushes

Preconditions

  • Offers turned on
  • App in the background

Steps

  1. Open the app › Notification settings
  2. Turn off "Offers & promotions"
  3. Go to the Home screen
  4. Send a promo from the staging console

Expected result

No notification arrives. The console shows the user has opted out of offers.

TC-PUSH-004No duplicate notifications after reinstall

Preconditions

  • User logged in on one device

Steps

  1. Delete and reinstall the app
  2. Log in and allow notifications
  3. Send "Order confirmed"

Expected result

Exactly one notification arrives. The server has one active token for this device.

TC-PUSH-005Push text in German has no placeholders

Preconditions

  • Phone language set to Deutsch

Steps

  1. Send each order event from the staging console
  2. Read every notification

Test data

Restaurant: Pasta Roma, driver: Marco

Expected result

All texts are German, names are filled in (e.g. "Pasta Roma bereitet dein Essen zu."), nothing is cut off.

TC-PUSH-006Offers respect quiet hours in the user time zone

Preconditions

  • Server time 20:30 UTC
  • Phone time zone Berlin (21:30 local)

Steps

  1. Send a promo from the staging console
  2. Change the time zone to New York (15:30 local) and send again

Expected result

Berlin: the promo is held until 08:00 local time. New York: the promo is delivered.

TC-PUSH-007Permission denied, then enabled in Settings

Preconditions

  • Fresh install

Steps

  1. Place the first order and tap "Don't Allow" on the permission dialog
  2. Open the app's notification settings
  3. Follow the link to system Settings and allow notifications
  4. Send "Picked up"

Expected result

The in-app screen explains notifications are off and links to Settings. After allowing, the push arrives.

TC-PUSH-008Badge clears after reading updates

Preconditions

  • App in the background
  • Badge is 0

Steps

  1. Send three order updates
  2. Check the app icon
  3. Open the app and the order
  4. Go back to the Home screen

Expected result

The icon shows 3, then the badge is gone after the order was opened.

Tip: click a test case to expand it.

Mobile vs web

  • One chance to ask. The permission dialog appears once per install. Test the “Don’t Allow” path carefully. To see the dialog again, delete and reinstall the app.
  • Settings › Notifications › App lets users turn off banners, sounds, badges, lock screen and previews separately. Try each.
  • Focus modes and Scheduled Summary can delay or bundle notifications. Time-sensitive ones may break through.
  • Simulator: xcrun simctl push or drag an .apns file onto the Simulator. A TestFlight or App Store build uses the production APNs environment, while a debug build from Xcode uses the sandbox, so a token from one doesn’t work with the other. A common “push doesn’t arrive” cause in testing.
  • Foreground: iOS doesn’t show the banner unless the app asks for it. Check the app does something.
  • Web push works in Chrome, Edge, Firefox and Safari through a background script called a service worker. On iPhone and iPad (iOS 16.4+), only for web apps added to the Home Screen.
  • The browser asks (“shop.example.com wants to show notifications”). Many browsers only allow this after a user click, and Chrome hides prompts from sites that ask too aggressively. Test the prompt is triggered by a clear button, not on page load.
  • Blocked is blocked. If the user clicks Block, the site can’t ask again; the user must change it in the browser’s site settings. The site should explain how.
  • Operating system settings can also block browser notifications (macOS, Windows Focus Assist).
  • Test in DevTools: in Chrome, Application › Service workers has a Push button that sends a test message to the service worker.
  • Unsubscribe and clearing site data must stop the notifications.

Checklist

Push notifications checklist

0 of 14 checked

Check your understanding

Quick quiz

0/5 answered · 0 correct

  1. 1.Who actually delivers a push notification to an iPhone?

  2. 2.Tapping a push opens the order when the app is in the background, but opens Home when the app was killed. What should you test next?

  3. 3.A user turned off "Offers" in the app but still gets promo pushes. Where is the bug most likely?

  4. 4.On iOS, the user tapped "Don't Allow" on the notification dialog. What should the app do later?

  5. 5.A promo arrives at 3 am for users in New York. Which requirement probably uses the wrong time?

Key takeaways

  • Push goes app → token → your server → APNs/FCM → device. Bugs can hide at every hop.
  • Permission is asked once on iOS (and denied for good after two “no”s on Android 13+), so ask in context and always offer a way back via Settings.
  • Test every push in foreground, background and killed states, and check that tapping opens the right screen.
  • Classic bugs: duplicates, opt-outs that don’t sync, raw placeholders or wrong language, badges that never clear, wrong time zone for quiet hours.
  • Trigger pushes yourself with a staging console, Firebase test messages or simctl push, and confirm on real devices before release.

Finished this page?Nice work, this page is complete!

Mark it as done to track your progress. It's saved in this browser, no account needed.

Its review cards are now in Bites ⚡
Esc

Type to search the whole site.