You get one notification permission prompt. Most apps spend it in the first ten seconds.
The system prompt is not one you can retry, and the default integration spends it on somebody who has been in your app for four seconds.
The system prompt is not a prompt you can retry. On both platforms, asking is a one-shot action with a permanent-ish consequence, and the default integration — call requestAuthorization on first launch — spends it on somebody who has been in your app for four seconds and has no idea what you would send them.
What each platform actually does
iOS. requestAuthorization shows the system alert exactly once per install. After a decline, every later call returns immediately with .denied and no UI at all. Your code cannot tell the difference between "the user just declined" and "the user declined eight months ago" without checking getNotificationSettings first. The only route back is Settings → your app → Notifications, which approximately nobody visits.
Android 13 and up (API 33). POST_NOTIFICATIONS is a runtime permission, so the same shape with a wrinkle: the system stops showing the dialog after two dismissals, and treats further requests as denied. shouldShowRequest PermissionRationale tells you when you are on the last one. Below API 33 notifications are granted at install and none of this applies — which is why this bug arrived for a lot of apps only when they bumped their target SDK.
Either way: you have one good ask, and after it is spent, it is spent.
Ask before you ask
The fix is old and well-evidenced, and it is still not the default in most apps: show your own screen first.
A screen you control has properties the system dialog does not. You can say what you will send — "we'll tell you when your order ships", not "Notifications". You can show it at a moment that makes the answer obvious, which is almost never first launch and almost always immediately after the person did something that implies they want to be told about it. And a "not now" on your own screen costs you nothing: you can ask again next week, next month, after they place their second order.
Only when they say yes do you call the system API. You have converted an irreversible one-shot into something you can retry, and you spend the real prompt on people who have already said yes to a softer version of the same question.
Two things worth getting right in the priming screen:
- Do not make it look like the system dialog. Users who feel tricked into the real one deny it, and both platforms' review guidelines take a dim view.
- Do not show it on first launch either. A priming screen at second zero is the same mistake wearing better clothes.
The iOS option almost nobody uses
UNAuthorizationOptions.provisional requests provisional authorization, which does not show a prompt at all. Notifications are delivered quietly straight to Notification Center — no banner, no sound, no lock screen — and each one carries "Keep" and "Turn off" buttons. If the person taps Keep, you are promoted to full authorization without ever having spent the dialog.
So you can start sending on day one, let the notifications themselves make the argument, and only ask properly when there is evidence somebody wants them. The trade is real: provisional notifications are quiet, so a time-critical one will not interrupt anybody. It suits a digest, a content feed, an order update — not a two-factor code.
There is no Android equivalent.
The thing to measure
Most teams measure opt-in rate at the prompt, which is the wrong denominator — it hides everybody the prompt was never shown to. Measure:
- what share of installs have been asked at all,
- of those, what share accepted,
- and what share of your whole install base is currently reachable.
The third is the one that matters, and it is the one that quietly falls over time as people upgrade phones and reinstall. An app with a 70% accept rate and a 20% ask rate is doing much worse than the first number suggests, and the fix is in the second.
Priming without building a screen
We ship in-app messages, which is largely how we ended up writing this: a rule you publish in a console, cached by the SDK and evaluated on the device, so it can fire on app open — the exact moment a server is not present for. A button on it can call the platform's own permission API, so the priming screen is something you author and re-target rather than something you ship a build for.
It is the one thing in this area a server-side product genuinely cannot do for you, because the server does not know when the app is open. Details in the in-app messages documentation.
We build Notibase — push, email, SMS, in-app messages and an in-app inbox to one audience through one API, on your own Firebase and APNs credentials.