Push Unsubscribe Rate Benchmarks and Churn

“Our unsubscribe rate is 0.4%” is a meaningless sentence until you know which of three unrelated events it counts and what sat in the denominator. This reference defines each one, shows how to compute them separately, and explains why a cohort retention curve is a better health signal than any single monthly number.

Quick Answer

Web push loses subscribers through three distinct channels, and each needs its own numerator and its own denominator. Adding them together and dividing by one number produces a figure that cannot be compared to anything, including your own previous month.

Channel Numerator Denominator You learn about it
Explicit unsubscribe opt-outs recorded by your app subscribers notified in the period Immediately
Permission revocation Notification.permission flipped away from granted active subscribers seen in the period Only on their next visit
Silent attrition endpoints returning 410 Gone or 404 endpoints attempted in the period On the next send attempt

Report all three, plus a cohort retention curve. A single blended “churn rate” hides which lever to pull.

Three Kinds of Attrition

Three attrition channels and their denominators Explicit unsubscribes are detected by your own preference centre against subscribers notified. Permission revocations are detected on the next visit against active subscribers. Silent attrition is detected by 410 Gone responses against endpoints attempted. Three kinds of attrition, three different denominators Explicit unsubscribe DETECTED BY your preference centre or a notification action DENOMINATOR subscribers notified during the period LATENCY immediate, you own it Permission revoked DETECTED BY Notification.permission read on the next visit DENOMINATOR active subscribers who returned in the period LATENCY days to never Silent 410 attrition DETECTED BY 410 Gone or 404 from the push service DENOMINATOR endpoints attempted during the period LATENCY only on the next send summing all three over one denominator produces a number with no meaning
The three channels differ in what causes them, how quickly you observe them, and what population they are a share of.

Explicit unsubscribe is a deliberate act inside your product: a toggle in a preference centre, an “unsubscribe” action button on the notification, or an account-deletion flow. It is the only channel where the user told you why they left, if you asked. It is also the only one you must log for compliance reasons — see GDPR-compliant push unsubscribe logging — and the one you can make friction-free with a one-click unsubscribe action on the notification itself.

Permission revocation happens in browser UI you do not control. The user clicks the padlock and blocks notifications, or resets site permissions wholesale. Your server learns nothing: the subscription may keep returning 201 for a while even though nothing will ever be displayed. The only detection is client-side — read Notification.permission on page load and report a change.

// on every page load for a known subscriber
async function reconcilePermission(subscriberId) {
  const state = Notification.permission;                 // 'granted' | 'denied' | 'default'
  const reg = await navigator.serviceWorker.ready;
  const sub = await reg.pushManager.getSubscription();
  if (state === 'granted' && sub) return;
  await fetch('/api/subscriptions/state', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      subscriberId,
      permission: state,
      hasSubscription: Boolean(sub),
      reason: state === 'granted' ? 'subscription-missing' : 'permission-revoked'
    })
  });
}

Silent attrition is everything else: the browser profile was deleted, the app data was cleared, the device was wiped, the push service garbage-collected an endpoint nobody had used in months. You discover it when the push service answers 410 Gone (or 404 Not Found), and only then. This is usually the largest of the three channels, and the one teams forget to count because it never produces a user-facing event. Retiring those endpoints correctly, and pruning the rows afterwards, is covered in handling 410 Gone responses at scale.

Getting the Denominators Right

Each rate answers a different question, so each divides by a different population.

  • Explicit unsubscribe rate answers “of the people we messaged, how many told us to stop?” Divide by subscribers notified at least once in the period, not by total list size. A dormant subscriber who received nothing cannot have opted out of it, and including them makes any send look safer than it was.
  • Revocation rate answers “of the people who came back to the site, how many had turned us off?” Divide by subscribers who loaded a page in the period, because that is the only population where you could have observed the state at all. Dividing by the full list produces a number that falls whenever traffic falls, which is exactly backwards.
  • Silent attrition rate answers “of the endpoints we tried, how many were dead?” Divide by endpoints attempted. This one is naturally per-send and is the right denominator for spotting a sudden purge by a push service.
-- three rates for one calendar month, each with its own denominator
WITH notified AS (
  SELECT DISTINCT subscriber_id FROM push_sends
  WHERE sent_at >= date_trunc('month', now()) AND state = 'accepted'
), seen AS (
  SELECT DISTINCT subscriber_id FROM page_sessions
  WHERE started_at >= date_trunc('month', now())
), attempted AS (
  SELECT count(*) AS n FROM push_sends WHERE sent_at >= date_trunc('month', now())
)
SELECT
  (SELECT count(*) FROM optouts o
    WHERE o.created_at >= date_trunc('month', now())
      AND o.subscriber_id IN (SELECT subscriber_id FROM notified))::numeric
    / NULLIF((SELECT count(*) FROM notified), 0)                       AS explicit_rate,
  (SELECT count(*) FROM permission_events p
    WHERE p.created_at >= date_trunc('month', now())
      AND p.permission <> 'granted'
      AND p.subscriber_id IN (SELECT subscriber_id FROM seen))::numeric
    / NULLIF((SELECT count(*) FROM seen), 0)                           AS revocation_rate,
  (SELECT count(*) FROM push_sends s
    WHERE s.sent_at >= date_trunc('month', now())
      AND s.response_status IN (404, 410))::numeric
    / NULLIF((SELECT n FROM attempted), 0)                             AS silent_rate;

Cohort Retention and Subscriber Half-Life

A monthly rate is a single scalar describing a population that is constantly being refilled by new subscribers. If acquisition doubles, the blended rate falls even though nothing about retention improved. Cohort curves remove that confound: fix a group by the week they subscribed, and track what share of them are still reachable at each subsequent week.

“Still reachable” means all three conditions hold — no explicit opt-out, permission still granted as far as you know, and the endpoint has not returned 410.

Cohort retention curves and subscriber half-life A cohort acquired through a soft prompt retains about 43 percent of subscribers at 26 weeks with a half-life near 19 weeks, while a hard-prompted cohort falls below 50 percent within about seven weeks. Cohort retention: share of subscribers still reachable 100% 75% 50% 25% 0% half-life ~19 weeks half-life ~7 weeks soft-prompted cohort hard-prompted cohort 0 4 8 12 16 20 26 weeks since subscribing (illustrative shapes, not industry data) half-life is far more robust than a single blended monthly rate
Two cohorts with similar first-week numbers can have half-lives three months apart. Only the curve shows it.

Subscriber half-life — the number of weeks until a cohort is 50% reachable — is the single most useful summary of that curve. It is easy to explain, robust to acquisition volume changes, and directly comparable between acquisition sources. A cohort acquired behind a soft ask that explains what will be sent typically has a much longer half-life than one acquired by prompting on page load, even if both produced the same number of subscriptions on day one.

-- weekly retention for each acquisition cohort
SELECT
  date_trunc('week', s.created_at)::date                      AS cohort_week,
  floor(extract(epoch FROM (d.day - s.created_at)) / 604800)  AS week_index,
  count(*) FILTER (WHERE s.churned_at IS NULL OR s.churned_at > d.day)::numeric
    / count(*)                                                AS reachable_share
FROM push_subscribers s
CROSS JOIN LATERAL generate_series(
  date_trunc('week', s.created_at), now(), interval '1 week'
) AS d(day)
GROUP BY 1, 2
ORDER BY 1, 2;

Two habits make these curves trustworthy. Set churned_at from whichever of the three channels fired first, and store the channel alongside it so you can decompose the curve later. And never backfill churned_at with the date you discovered a dead endpoint — a 410 found in March on a subscriber last messaged in November tells you the loss happened somewhere in between, so attribute it to the last successful send, and treat the interval as measurement uncertainty rather than pretending to precision.

What Drives Unsubscribe Spikes

Steady-state attrition is mostly silent and mostly out of your hands. Spikes are almost always yours.

Daily unsubscribe rate with two cadence incidents Daily explicit unsubscribe rate sits in a narrow steady-state band except on day seven, when three sends went out in one day, and day fourteen, when an unsegmented broadcast reached the whole list. Daily explicit unsubscribe rate over three weeks 0.4% 0.3% 0.2% 0.1% 0.0% day 7: three sends inside a single day day 14: unsegmented broadcast to the whole list steady-state band 1 4 7 10 14 17 21 day of the campaign period spikes are cadence and relevance events, not gradual list decay
Two incidents dominate three weeks of attrition, and both were self-inflicted scheduling decisions.

The recurring causes, roughly in order of how often they turn up in incident reviews:

  • Cadence breaks. Two or three sends in one day is the fastest way to lose a list. Enforce a hard limit with push notification frequency caps rather than relying on a shared calendar.
  • Relevance breaks. A broadcast to the whole list about something only one segment cares about. The unsubscribes concentrate in the segments the message did not apply to, which the daily total hides — always decompose a spike by segment.
  • Local-hour mistakes. A send that lands at 02:00 for a subset produces a distinctive spike the following morning. Correct scheduling is covered in timezone-aware push notification scheduling.
  • A new prompt going live. A more aggressive permission prompt raises acquisition and lowers cohort quality at the same time, so the blended rate worsens weeks later. This is another reason to read cohorts rather than blends.
  • Push service purges. A step change in 410 volume across one provider on one day is not your users leaving; it is a provider expiring endpoints. Segment your silent-attrition rate by endpoint host so this is visible.
  • A re-engagement campaign that overreaches. Win-back sequences to dormant subscribers legitimately produce higher opt-out rates than regular sends; budget for that when planning win-back campaigns for dormant users.

What Counts as Healthy

Be sceptical of any source quoting a precise industry unsubscribe rate. Published figures blend the three channels above, use undisclosed denominators, and come from vendors whose customer mix is nothing like yours. The honest way to state a target is as a range, with the definition attached:

  • Explicit unsubscribe per send, over subscribers notified, is typically a fraction of a percent for a relevant, well-paced programme. Anything approaching a full percentage point on a routine send is a cadence or relevance problem worth investigating that week.
  • Monthly silent attrition, over endpoints attempted, is usually the largest channel and can comfortably run several percent. It is driven by device and browser lifecycle far more than by your content.
  • Subscriber half-life for a healthy consumer list is normally measured in months, not weeks. A half-life under a month means the acquisition flow is producing subscribers who never wanted the notifications.

Set your own thresholds from your own first three months of data, then alert on deviation from your baseline rather than on an absolute number. When you compare periods, use the guardrail discipline described in statistical significance for push notification tests — day-to-day movement in a small numerator is mostly noise.

Gotchas & Edge Cases

  • One person, several endpoints. A subscriber with a phone and two laptops holds three subscriptions. Endpoint-level attrition overstates people-level churn; report both and label them clearly.
  • 410 on a stale endpoint is old news. The loss happened whenever the browser discarded the subscription, not on the day you tried to send. Attribute it to the last successful send.
  • Re-subscribing looks like churn plus acquisition. A user who clears site data and re-grants permission produces a new endpoint. Deduplicate on the user id before computing cohort curves, or your churn and acquisition will both be inflated.
  • A denied prompt is not churn. Someone who never granted permission was never a subscriber. Keep prompt-conversion metrics entirely separate from attrition metrics.
  • Do not delete the row on unsubscribe. Keep a tombstone with the timestamp and channel; you need it for the cohort curve, for suppression, and for the compliance record.
  • Seasonality is real. New-device periods after major holidays produce a visible bump in 410 volume as old profiles are abandoned. Compare like-for-like periods before declaring a regression.

FAQ

What is a good unsubscribe rate for web push notifications?

There is no single credible industry figure, because published numbers blend explicit opt-outs, permission revocations and dead endpoints over undisclosed denominators. For a relevant, well-paced programme, explicit opt-outs over subscribers notified usually sit in a fraction of a percent per send, while silent attrition over endpoints attempted can run several percent a month. Establish your own baseline in the first three months and alert on deviation from it.

How do I detect when a user revokes notification permission?

Only from the client. Read Notification.permission on page load together with pushManager.getSubscription, and report any state that is not granted-with-a-subscription back to your server. There is no server-side signal for a revoked permission, and the push service may keep accepting requests for an endpoint whose notifications will never be displayed.

What is subscriber half-life and why use it?

It is the number of weeks until an acquisition cohort is 50 percent reachable, counting explicit opt-outs, revocations and dead endpoints together. Unlike a blended monthly rate it does not move when acquisition volume changes, so it compares cleanly between acquisition sources and across time.