Soft Push Requests: How to Never Waste Your One-Shot Permission Dialog

Stan
Stan

02 Mar 2025

On iOS, the system push notification permission dialog can only be shown once. There is no second chance. If a user taps "Don't Allow" on that native alert, your app loses the ability to reach them through push notifications — permanently, unless they manually navigate through Settings. And almost nobody does that. This article covers a proven technique called a soft push request that protects your one-shot permission dialog and gives you control over when — and whether — the system prompt ever fires.


The Cost of a Wasted Permission Dialog

Let's be concrete about what happens when a user taps "Don't Allow." The only recovery path requires the user to:

  1. Leave your app and open device Settings.
  2. Scroll through a long list of apps to find yours.
  3. Tap into your app's settings page.
  4. Find the Notifications toggle and enable it.

In practice, almost no one completes that journey — a denied permission is effectively permanent. The real problem is timing. Many apps fire the system dialog on first launch, before the user has any reason to trust the app.

A push permission denied on day one is a marketing channel lost for the lifetime of that install.

The Soft Push Pattern Explained

  1. Your app shows a custom in-app popup

    Amply fires a deeplink like happens://soft-push-request; your app listens for it and renders its own UI asking, for example, 'Want to receive notifications about deals and updates?' with Yes and No buttons. Amply decides when to trigger; your app owns the visual layer.

  2. User taps Yes

    Now your app triggers the real system permission dialog. The user has already expressed intent, so they are far more likely to tap 'Allow.'

  3. User taps No

    Do NOT trigger the system dialog. You have saved your one-shot permission for a later time when the user may be more engaged.

Teams that adopt this pattern protect the system prompt for users who have already self-selected as willing — and keep it in reserve for everyone else. Instead of guessing a single moment at first launch, you get to choose the moment per user.


When to Show the Soft Push

Session-based targeting

Show the soft push prompt starting from the user's third session. By session three, the user has returned to your app multiple times, which signals genuine interest.

Event-based targeting

Trigger the soft push after a meaningful in-app action:

  • E-commerce: After the user adds their first item to favorites.
  • Fitness: After the user completes their first workout.
  • Content: After the user bookmarks or saves an article.

Setting Up with Amply

  • When — Triggering Event: SessionStart, with Repeat Rules set to on 3 globally (fires on the user's third lifetime session).
  • Who — Custom Property push_permission_asked (Boolean) = false: exclude users who have already seen the soft push.
  • Who — Custom Property push_enabled (Boolean) = false: exclude users who already granted push permission.
  • Frequency Limits: Show campaign 1 time total.

Code Implementation

Initialize custom properties

// Android (Kotlin). Set initial properties on first launch.
amply.setCustomProperties(mapOf(
    "push_permission_asked" to false,
    "push_enabled" to false,
))

Handle the soft push deeplink

Amply's action type is Deeplink — the SDK fires a URL into your app when the campaign matches; your app decides what to render. Below, the listener checks for the campaign's deeplink, shows your custom popup, and writes the response back as Custom Properties so the same user is not prompted again. On Android 13+, the system permission is the POST_NOTIFICATIONS runtime request; on iOS it is UNUserNotificationCenter.requestAuthorization(options:); in Expo / React Native, Notifications.requestPermissionsAsync().

// Android (Kotlin). Register a DeepLinkListener; the host app renders the soft popup.
import tools.amply.sdk.actions.DeepLinkListener

amply.registerDeepLinkListener(object : DeepLinkListener {
    override fun onDeepLink(url: String, info: Map<String, Any>): Boolean {
        if (!url.contains("soft-push-request")) return false

        showSoftPushDialog(
            onAccept = {
                // User said YES — now trigger the real system dialog
                // (Android 13+ POST_NOTIFICATIONS runtime permission request).
                requestPostNotificationsPermission()
                amply.track("SoftPushResponse", mapOf("accepted" to true))
                amply.setCustomProperties(mapOf(
                    "push_permission_asked" to true,
                    "push_enabled" to true,
                ))
            },
            onDecline = {
                // User said NO — preserve the system dialog for later
                amply.track("SoftPushResponse", mapOf("accepted" to false))
                amply.setCustomProperties(mapOf(
                    "push_permission_asked" to true,
                ))
            },
        )
        return true
    }
})

Measuring Push Permission Rates

  1. Soft push impressions: How many users saw the custom popup.
  2. SoftPushResponse (accepted: true): How many tapped Yes.
  3. SoftPushResponse (accepted: false): How many tapped No — their system permission is still intact.
  4. Actual push permission granted: Of those who tapped Yes, how many also tapped Allow on the system dialog.

Conclusion

The system push notification permission dialog is one of the most consequential UI moments in your app. It is irreversible, uncontrollable, and unforgiving. The soft push pattern gives you control by placing a custom dialog in front of the system prompt.

Combined with Amply's Repeat Rules on SessionStart, Custom Property filters, and Frequency Limits, you can reconfigure the entire flow from the dashboard — timing, targeting, caps — without a new release. The soft popup and the native permission call still live in your app, but when and to whomthey happen is now remote.