Blog · Engineering
Expired push subscriptions: why they happen and how to keep your audience
Why push subscriptions expire, what that means for your metrics, how to see them in the dashboard, API and webhooks, and how to win back people who return.
A push subscription expires when it can no longer reach that browser: the person cleared your site's data, revoked permission, switched browsers, or the browser replaced it with a new one. Every audience loses subscriptions this way; what matters is reporting it honestly and winning back people who return with permission still granted.
In short:
- Subscriptions expire because of what people do (clear data, revoke permission, reinstall the browser) or what browsers do (renew them, or retire them after a long time unused).
- An expired subscription cannot be revived: only the browser can create a new one.
- Measure your audience in active subscribers and reachable people, never in all-time sign-ups.
- castly marks them expired automatically, with the
expiredstatus and thesubscriber.expiredwebhook.- To recover: subscribe again on the next visit when permission is still granted, attach your user ID, and don't over-send.
Why a subscription stops working
When someone accepts your notifications, their browser creates a subscription: a unique address on that browser's push service (Google's for Chrome, Mozilla's for Firefox, Apple's for Safari, Microsoft's for Edge) that identifies the device and your site. That address does not last forever:
- Site data cleared. Clearing cookies and site data for your domain also removes the service worker and the subscription.
- Permission revoked. The person blocks your notifications in settings. This is the cause you influence most.
- Browser reinstalled or new profile. A fresh profile does not inherit the old one's subscriptions, even on the same computer.
- Browser renewal. The browser may swap the subscription for a new one; the old one stops working and the new one does not exist for your site until it is registered.
- Long inactivity. Push services may retire subscriptions on devices that have not connected for a long time.
- Platform-specific cases. On iPhone, removing the installed web app from the Home Screen deletes its subscription; in Safari, a push that shows no notification gets the subscription revoked, as covered in web push in Safari on macOS.
One case is not an expiration: when the person clicks your "Turn off notifications" button and your site calls Castly.unsubscribe(), the subscriber becomes unsubscribed. That is an opt-out, and it deserves its own count.
What it means for your metrics
If you add up every sign-up since day one, you mix devices that no longer exist with devices that still receive notifications, and every figure built on that total (reach, click-through rate, cost per notification) stops reflecting reality. Keep three numbers apart:
- Active subscribers: devices that, as far as anyone knows, can still receive notifications.
- Expired: devices that can no longer be reached. Their trend tells you whether you are losing audience faster than before.
- Reachable people: users with at least one active device. If someone subscribed on a phone and a laptop and only the laptop expires, they are still reachable.
One nuance for your reports: the push service only says a subscription is gone when someone tries to deliver a message to it, so a dead subscription keeps counting as active until the next send to that device. That is why active subscribers often drop sharply right after a campaign to your whole audience: you did not lose those people that day, you just found out. Compare months with a similar sending pace, and watch expirations per send against your own average: a steady trickle is healthy; a sudden jump deserves a closer look.
How to see expired subscriptions
In castly there is nothing to build to detect them: when the push service says a subscription no longer exists, the subscriber moves to the expired status and stops receiving sends. You see it in three places:
- In the dashboard. The overview shows active subscribers and sign-ups in the last 7 days. Under Subscribers you can filter by status (active, expired or unsubscribed), browser or tag, and each notification's detail counts how many devices turned out to be expired on that send.
- Through the API. From your server, with the secret key,
GET /v1/subscribers?status=expiredreturns expired subscribers; adduserIdto check whether a person still has an active device. Parameters are in the API reference. - Through a webhook. Every time a subscriber expires you receive a
subscriber.expiredevent withsubscriberIdandexternalUserId, the user ID you attached. Setup and signature checks are in the webhooks docs.
// After verifying the webhook signature
const event = JSON.parse(rawBody);
if (event.type === 'subscriber.expired') {
const { subscriberId, externalUserId } = event.data;
await db.devices.markExpired(subscriberId);
if (externalUserId) await refreshChannels(externalUserId);
}
A common use: when someone has no active devices left, your app moves their important alerts to email. Always verify the signature before processing the event, and since webhooks are not retried, reconcile your data with the API from time to time.
How to win back people who return
No server can revive an expired subscription: only the person's browser can create a new one. Your chance is the next visit, and what to do depends on Castly.permission():
granted: permission is still in place, even though the subscription was lost or renewed. CallingCastly.subscribe()again creates a new subscription without showing any dialog; if the current one is still alive, it simply confirms it.default: the person reset your site's permissions. Treat them as a new visitor and let your opt-in prompt appear when it normally would.denied: don't push it; the browser will not let you ask again. Only the person can change it in their settings.
Castly.init({ publicKey: 'pk_live_…' });
// Once per session, and only when permission is already granted
if (Castly.permission() === 'granted' && !sessionStorage.getItem('push-ok')) {
sessionStorage.setItem('push-ok', '1');
Castly.subscribe({ userId: currentUser?.id }).catch(() => {
reactivateButton.hidden = false; // some browsers require a click
});
}
One check per session is enough: if the subscription is still alive, it is recognized and no duplicate is created. Some browsers only allow subscribing after a user gesture; if the call fails on page load, show a "Turn notifications back on" button and run it again from its click.
Attach every subscription to your user
Each new subscription is a new subscriber with a new ID. If your system only stores that ID, the returning person looks like someone else and their history is split in two. The fix is to attach every subscription to your own user ID: pass it in subscribe({ userId }), or call identify if the person signs in after subscribing.
// On sign-in, in a browser that was already subscribed
await Castly.identify(user.id, { plan: user.plan });
From then on, when you send to { "type": "users", "userIds": ["u_123"] }, the notification reaches every active device that person has, including the one that just came back, and your server never needs to know which subscription expired or what replaced it. Use an internal identifier, not an email address: you do not need personal data for this.
How to reduce expirations
Nobody can stop someone from wiping their laptop, but revoked permission is on you: every irrelevant notification is an invitation to block your site, and a block turns an active device into an expired one for good.
- Send less, and better. Bundle updates into a digest instead of sending each one on its own.
- Segment. Send only to people who care with
{ "type": "segment", "tags": { "interest": "deals" } }. - Offer preferences. Let people pick topics or frequency and store them as tags: someone who can turn the volume down has no reason to block.
- Ask for permission with context. People who accept without understanding what your notifications are for regret it quickly.
Frequently asked questions
Is it normal to see expired subscriptions on every send?
Yes. People clear data, change computers and reinstall browsers all the time, so there will always be a trickle. What deserves attention is a sudden jump compared with your own trend.
Why do my active subscribers drop right after a campaign?
Because a dead subscription is only discovered when someone tries to deliver a message to it. A campaign to your whole audience reveals, all at once, the subscriptions that expired over the previous weeks; it is not a loss from that day.
What is the difference between an expired subscription and an unsubscribe?
An expired subscription (expired) stopped working without your site doing anything: cleared data, revoked permission or a new browser. An unsubscribe (unsubscribed) happens when your site unsubscribes the person, usually because they asked for it.
Do I need to delete expired subscribers?
No: an expired subscriber no longer receives sends and does not count as active. If your data retention policy requires it, you can delete it from the dashboard.