When launching a mobile app with subscriptions, a typical situation: the payment fails, the user doesn't even notice, the app remains silent. Up to 60% of customers churn on the first auto-payment failure. Without a notification chain, recovering the subscription is nearly impossible. A well-designed push notification system retains up to 40% of users on the verge of unsubscribing. Let's break down how to set up subscription expiry notifications using StoreKit 2, FCM, and proper segmentation. Our experience: 5+ years and 50+ subscription projects.
Why One Push Notification Is Not Enough
A single notification is a poor strategy. The user may ignore or postpone it. Research shows that a cascade of 3–4 reminders boosts renewal conversion by 30%. We use a chain: 7 days before → 1 day before → on the day of expiry → 3 days after. Each step delivers a specific call-to-action with a deeplink.
Server-Side Events vs. Local Notifications
The first question: how do you know when the subscription expires? For In-App Purchase (StoreKit), Apple sends events to your server via App Store Server Notifications V2. For custom server-side subscriptions, the logic is on your end.
StoreKit 2 / App Store Server Notifications V2: Apple delivers RENEWAL and EXPIRED events to your server. Upon receiving DID_FAIL_TO_RENEW — the first signal to send a push. At GRACE_PERIOD_EXPIRED — the last chance. Apple sends some system notifications on its own, but relying solely on them is insufficient — you must build your own reminder business logic.
// Server notification V2 (decoded payload) { "notificationType": "DID_FAIL_TO_RENEW", "subtype": "GRACE_PERIOD", "data": { "bundleId": "com.example.app", "transactionInfo": { ... } } } App Store Server Notifications
Local notifications are only suitable if you have no server at all, or for offline scenarios. Use UNUserNotificationCenter with UNCalendarNotificationTrigger — schedule a notification on expiresDate - 3 days. Problem: if the user renews via web or another device, the local notification will still fire. Without server synchronization, this leads to false positives.
| Criteria | Server Push | Local Notifications |
|---|---|---|
| Accuracy | High | Low (false positives) |
| Server dependency | Required | Not needed |
| Deeplink support | Yes | Yes |
| Chain flexibility | Maximum | Limited |
| Channel | APNs / FCM | UNUserNotificationCenter |
Server push is 3–4 times better than local in delivery accuracy and configuration flexibility.
Reminder Chain
A working scheme for B2C:
- 7 days before expiry: "Your subscription expires April 15 — renew to keep your data"
- 1 day before: specific call-to-action with deeplink to the subscription management screen
- On expiry day: notification with a limited-time offer (if available)
- 3 days after: reactivation with a discount (optional)
A deeplink in the notification is mandatory. A push without action loses conversion. On iOS: UNNotificationAction with foreground — opens the app and passes userInfo. On Android: PendingIntent with the appropriate Intent.
// iOS: handle tap on subscription notification func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async { let info = response.notification.request.content.userInfo if info["type"] as? String == "subscription_expiry" { NavigationRouter.shared.navigate(to: .subscriptionManagement) } } On Android via FCM, similarly: pass type: subscription_expiry in the data payload, route in FirebaseMessagingService.onMessageReceived.
Implementation details on Android (FCM)
class MyFirebaseMessagingService : FirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { val type = message.data["type"] if (type == "subscription_expiry") { // Open subscriptions screen val intent = Intent(this, SubscriptionActivity::class.java) startActivity(intent) } } } What to Do If Push Is Not Delivered?
Check notification permissions (ATT on iOS, settings on Android), device token status, and deeplink correctness. Consider the user's timezone — send pushes during local daytime. Implement retry logic with a manual trigger.
Segmentation and Timing
A user with a monthly subscription and a user with a yearly subscription require different approaches. For monthly, a 7-day window is 25% of the remaining term — too aggressive. For yearly, 7 days out of 365 is fine.
| Subscription Type | Reminder Window | Aggressiveness |
|---|---|---|
| Monthly | 3–5 days | High |
| Yearly | 7–14 days | Low |
| Weekly | 1–2 days | Medium |
The user's timezone is critical: a push at 3 AM causes irritation and unsubscribes. Firebase FCM allows setting delivery_time based on the device's local time. For APNs, this is handled server-side via a task scheduler that respects the user's timezone.
Our Work Process
- Audit your current subscription system: StoreKit / server-side / hybrid.
- Configure App Store Server Notifications V2 or server webhooks for expiry triggers.
- Implement the push notification chain with deeplinks to the appropriate screen.
- Test on StoreKit sandbox environment and live FCM.
- Monitor and adjust timing based on analytics.
What's Included in the Work
- Setup of App Store Server Notifications V2 / server webhooks
- Development of push notification chain (up to 4 stages)
- Integration of deeplinks to the subscription management screen
- Event logging for analytics
- Documentation and instructions for your team
- 1 month of support after launch
Timeline Estimates
Integration with an existing notification server and StoreKit 2 — 2–3 days. Building server-side logic from scratch (scheduler, user segmentation) — up to 1 week.
Losing one user with a yearly subscription costs on average $300–$500, and a notification cascade brings back 15–20% of them. Implementing the notification chain increases LTV by 20–30% and reduces new user acquisition costs by 15%.
Get a consultation for your project — contact us to assess the scope and propose a solution. Request a free audit of your current subscription system.







