Back to Knowledge Hub
Platform

Web Push Subscription Lifecycle and Privacy

Manage Push API subscriptions from permission and endpoint creation through renewal, unsubscribe cleanup, and privacy-safe retention.

BotBrowser Team

Documentation

Want the structured docs for Platform?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

Web Push lifecycle from permission and subscription creation through endpoint rotation, unsubscribe cleanup, and privacy review

Web Push is not a permanent permission slip or a durable user identifier. A browser can grant notification permission, create a subscription with an endpoint and key material, rotate that subscription, and later remove it. BotBrowser can carry a declared permission state in an authorized, controlled profile so a team can inspect its own application flow. It does not grant permission, deliver a message, decide whether a user wants notifications, or delete an endpoint from a push service on the application's behalf.

The Push API specification, MDN Push API reference, and Service Workers specification describe the browser-side objects and events. The matching BotBrowser permissions documentation covers permission state as a profile input. This article adds the lifecycle and data-retention layer that page does not cover: how to bind a subscription to an application record, detect stale endpoints, clean up after unsubscribe(), and limit the privacy impact of endpoint and key material.

TL;DR

  • Treat Notification.permission, PushSubscription, and the server record as three related but different states.
  • Create a subscription only after a visible user action and a clear explanation of what future notifications mean.
  • Store the endpoint and public key material for the declared purpose, with an owner, expiry policy, and deletion path.
  • Reconcile pushsubscriptionchange, send failures, logout, account closure, and unsubscribe() instead of assuming a subscription lasts forever.
  • A push endpoint is a routing capability and a linkable value; do not use it as an identity key or retain it after the purpose ends.

Contents

Separate permission, subscription, and delivery state

The browser exposes a permission state through the Notifications API, while Push API operations create a PushSubscription for a service worker registration. These are not interchangeable. Notification.permission === "granted" means the browser permits notification use in the current context; it does not mean that a subscription exists, that the user is signed in, or that a provider will deliver a message. A subscription can be absent after a user grants permission, and it can become unusable while permission remains granted. The Permissions Policy guide separates policy eligibility from the permission state recorded here.

Keep three state machines in the application record. The permission state can be default, denied, or granted as exposed by the browser. The subscription state can be none, active, rotating, unsubscribed, or expired. The delivery state can be unknown, accepted, rejected, or stale according to the push service response. A single column called push_enabled collapses decisions that have different owners and makes cleanup impossible to audit.

The service worker registration is another boundary. registration.pushManager belongs to a registration, and registration.pushManager.getSubscription() resolves either to a PushSubscription or null. A null result is an observation at that registration and origin. It is not proof that the person has never subscribed elsewhere, that an account has no endpoint, or that notification permission is denied. The application should read permission and subscription state together, then explain the next action without guessing about identity.

Use an application record that can represent an unknown result. A useful minimum is a random internal record ID, the origin and service-worker scope, a redacted endpoint reference for deduplication, the public key material required by the protocol, the creation and last-seen times, the current lifecycle state, and the deletion reason. Do not put a full endpoint in analytics dimensions or expose it in a URL. Keep account linkage separate from the endpoint so an endpoint rotation cannot silently merge two accounts.

The distinction matters during support work. A person may have granted permission but never completed subscription creation because the service worker was not ready. Another may have an active subscription whose provider has rejected recent sends. A third may have called unsubscribe() locally while a server row remains. Each case needs a different message and owner: the browser, the page and worker, the push service, or the application database.

Create a subscription with explicit intent

A subscription request should follow a user-visible explanation and a deliberate action. Describe the category and cadence of notifications, the way to turn them off, and whether the service stores an endpoint for this account or device context. Do not call subscribe() on page load, after a hidden timer, or as a side effect of opening an unrelated feature. A browser permission prompt is a meaningful user decision, not a setup detail to hide.

The service worker must be registered before the page asks its PushManager for a subscription. The application should wait for navigator.serviceWorker.ready, verify that the registration is the intended scope, and then check getSubscription(). If a subscription already exists, compare its current endpoint and key material with the server record through an authenticated, purpose-limited exchange. If there is no subscription and permission is not granted, present the explanation and let the person choose whether to continue.

The visible control should have an off path that is as clear as the on path. “Enable order updates” is more precise than “Allow notifications.” A settings page should show the current application state, link to browser-level controls for a denied permission, and explain that turning off notifications may happen in either place. Never tell a person that an application switch can reverse a browser-level denial; the browser owns that decision.

The applicationServerKey associates the subscription with the service's public key. Keep the key configuration stable for the application purpose, and version it when a deliberate migration is needed. A changed key may yield a new subscription and should not be treated as an accidental duplicate. Record the key version in the server row, but do not log private key material or copy a complete subscription object into support systems.

Use an authenticated registration request that is safe to repeat. The client can send the endpoint, the protocol-required public keys, the origin context, and a short-lived application request ID. The server verifies the account and purpose, applies an idempotency rule, and returns the row's lifecycle state. It should not accept an endpoint as proof that the caller owns an account. A subscription is a capability for routing a push message, not a login credential.

When creation fails, distinguish a denied prompt, an unavailable service worker, a policy or secure-context failure, a provider error, and an application response error. The user-facing fallback should preserve the current task and offer ordinary in-app updates where appropriate. Do not keep reopening a prompt after a denial, and do not send a raw endpoint or exception string to a telemetry stream that is accessible to unrelated operators.

Model endpoint rotation and service worker events

The endpoint in a PushSubscription can change. The Push API exposes a pushsubscriptionchange event on the service worker global scope for a subscription that is no longer valid or has changed. Implement the handler as a reconciliation signal, not as permission to assume that every browser emits it in every circumstance. The worker should collect the current subscription when available, send a bounded update to the server, and mark the old row for review when it cannot recover a replacement.

The service worker event runs outside a page's normal lifecycle. A worker can be stopped and started, an update can replace its script, and a pending network request can be interrupted. Keep the event body small and retry only through a bounded, authenticated path. If the browser does not expose a replacement subscription, the server must retain an “unknown rotation” state rather than inventing an endpoint. A later page visit can reconcile getSubscription() and close the gap.

Do not treat a new endpoint as a new person. Rotation can reflect browser maintenance, provider policy, a changed key, profile reset, or service-worker replacement. Link the new row to the old row with a short-lived migration reference or a server-side record ID, then apply the same account and purpose checks. Retire the old endpoint as soon as the replacement is accepted, subject to the delivery provider's retry rules.

Delivery responses are another reconciliation input. A provider response that says an endpoint is gone or invalid is a reason to mark the row stale and stop sending to it. A transient failure is not proof that the endpoint should be deleted; apply the provider's documented retry policy and a bounded backoff. A successful send is also not proof that the user saw, opened, or still wants the notification. Keep delivery acknowledgement separate from user engagement.

An updated service worker can change the code that handles push and notificationclick. Coordinate that release with subscription schema changes. If the new worker expects a field that an old server row does not contain, maintain a compatibility path or delay the rollout. The Service Workers specification defines registration and activation behavior, but the application owns whether an old and new worker can safely process the same subscription record. The service worker cache lifecycle guide shows why activation and storage cleanup also need explicit owners.

The browser may also clear site data or a profile. A page can then observe getSubscription() === null while the server still has a row. Mark the row as unconfirmed, request reconciliation on the next explicit opt-in, and expire it after the documented retention window. Do not silently create a new subscription during a background visit just because the old row exists.

Clean up unsubscribe and account transitions

PushSubscription.unsubscribe() removes the subscription from the browser's local push manager and resolves a boolean result. A true result means the local operation succeeded; it does not delete a server row, erase provider logs, or prove that a queued message cannot be delivered. The application must send a separate authenticated deletion or disable request, and it must handle an offline client whose server update arrives later.

Use an idempotent cleanup endpoint. It should accept the application record ID or a server-generated deletion token, verify account ownership, mark the row inactive, and remove the endpoint and key material from active delivery storage. A tombstone can retain a random row ID, deletion time, and reason for the period required to prevent accidental reactivation, but it must not retain the complete endpoint merely for convenience.

Account logout and account closure have different meanings. Logout can disable account-specific delivery for the current session while leaving a user-controlled device subscription available for another approved account choice. Account closure should revoke all rows owned by the account according to the product's retention policy, notify the provider through its normal send-management path, and remove application copies. Do not infer either event from a browser tab closing or a worker being stopped.

A permission change is also not the same as unsubscribe. A person can deny notifications in browser settings while a server row remains; the next application visit should show the mismatch and stop sends until a new explicit choice. Conversely, an application switch can disable delivery while browser permission remains granted. Make the distinction visible so support does not ask a person to grant permission again when the actual issue is an inactive server row.

Design cleanup for failures. If the browser operation succeeds but the network request fails, queue only a minimal, expiring reconciliation marker and retry after the next authenticated visit. If the server marks the row inactive but the browser operation fails, explain that local browser cleanup is still pending and offer a retry from the settings screen. Never claim “deleted everywhere” unless the application has evidence for each system it controls; a remote push provider may have its own retention and queue semantics.

Minimize endpoint privacy and retention

An endpoint is usually a URL-like routing value containing an opaque token. Its opacity does not make it harmless: a service can use it to address a subscription, and a stable value can link activity over time. Treat the endpoint and public key as personal data or a sensitive pseudonymous identifier under the product's privacy review. Do not publish it, put it in a browser fingerprint, or use it to infer a device, location, or person.

Keep the full endpoint in a narrowly access-controlled delivery store. Application analytics need only a lifecycle event, purpose category, and non-reversible internal record ID. A support view can show the last-seen time, current state, key version, and a redacted suffix if an operator must distinguish rows. Avoid copying endpoint values into error messages, screenshots, URLs, client-side logs, or long-lived exports.

Choose a retention period from the user purpose. An active subscription can remain while the account has opted in and the service needs to send the declared category. A stale, unconfirmed, or unsubscribed row should have a short expiry. Delete or irreversibly redact endpoint and key material when the purpose ends, when an account is closed, or when the retention timer expires. Record the deletion reason without retaining the value that was deleted. Name the exact retentionUntil field and the accountClosed trigger in the policy so an operator can verify the deadline.

Limit access by function. The worker and delivery service need the endpoint and encryption inputs to send a message. Product analytics, support, and experimentation do not. Separate encryption keys, audit access, and redact values before a record leaves the delivery boundary. A database backup can extend retention unexpectedly, so include endpoint fields in backup deletion and restoration procedures rather than assuming the primary row is the only copy.

Notification content deserves the same discipline. Keep private details out of a push message when a short opaque reference can fetch the current item after the user opens the application. A notification can appear on a shared lock screen, and the push service may process metadata outside the application. State the minimum content needed for the declared use and provide an in-app detail page behind normal authorization.

Do not use lifecycle events to build a behavioral profile. “Subscribed,” “rotated,” “clicked,” and “unsubscribed” are useful operational states, but joining them with unrelated browser signals can create a persistent identity record. Keep the event vocabulary purpose-limited, aggregate where possible, and delete abandoned rows. The browser permission and fingerprinting article explains why permission state is a compatibility observation rather than a person identifier; the subscription lifecycle adds a concrete endpoint-retention risk.

Use an evidence-led lifecycle record

An evidence record should help an operator answer one question: which owner must act next? Capture the declared purpose, origin, service-worker scope, permission observation, subscription state, key version, redacted record ID, and the application action. Add a timestamp and a retention deadline. Do not capture the full endpoint unless the delivery service requires it in its controlled store.

Use a compact record shape that can be compared across releases:

{
  "purpose": "order-status",
  "origin": "https://shop.example",
  "serviceWorkerScope": "/app/",
  "permission": "granted",
  "subscriptionState": "active",
  "keyVersion": "v2",
  "recordId": "psr_7f2c",
  "lastReconciledAt": "2026-10-25T14:00:00Z",
  "retentionUntil": "2026-11-24T14:00:00Z",
  "nextAction": "send"
}

The values above are a fixture shape, not a claim about a live subscription. Keep a real endpoint out of examples. The decision rule is straightforward: if permission is granted but the subscription is none, offer an explicit subscribe action; if the subscription is active but delivery is stale, stop sends and reconcile; if the row is unsubscribed, delete active routing data and do not resubscribe silently; if retention has expired, remove the row and require a new user decision.

Use a lifecycle table to align ownership:

ObservationMeaningOwnerSafe next action
permission=granted, subscription=nullBrowser permits notifications but no local subscription is presentPage and userExplain and offer explicit opt-in
subscription=active, provider says goneDelivery route is no longer usableDelivery serviceMark stale and reconcile on next visit
unsubscribe() returned true, server row activeLocal cleanup finished; server cleanup is pendingApplication APIRetry idempotent disable request
permission=denied, server row activeBrowser choice conflicts with delivery intentUser and applicationStop sends and show browser-settings guidance
retention deadline passedPurpose ended or row was abandonedData ownerDelete endpoint and key material

This table is more useful than a green “push enabled” badge because each row names a decision and an owner. Keep the same vocabulary in support messages, release notes, and dashboards. When a browser or service-worker update changes the observed state, compare the first changed field and review the lifecycle rule rather than treating the difference as a user identity signal.

BotBrowser can help reproduce an authorized profile's declared permission input and open an owned page where the application records its own state transitions. It cannot summon a native prompt, force a user choice, inspect a provider's private queue, guarantee pushsubscriptionchange delivery, or certify that an endpoint was deleted from every external system. Use it for bounded compatibility observations, then rely on the application and provider contracts for lifecycle and retention decisions.

FAQ

Does granted notification permission mean a user is subscribed?

No. Permission allows notification-related operations in the current browser context. A subscription still has to be created for a service worker registration, and the server must accept its registration. The application should read getSubscription() and its own row before showing an enabled state.

Is a push endpoint a device fingerprint?

It is not proof of a device or person, but it is a linkable routing value. A stable endpoint can connect events in the delivery store, so limit access, retention, and joins. Never combine it with unrelated browser signals to build an identity profile.

What should happen after unsubscribe()?

Treat the browser result and server cleanup as separate steps. Mark the local state inactive, send an authenticated idempotent disable request, remove active endpoint and key material, and retain only a minimal tombstone if the product needs to prevent accidental reactivation. Do not claim that provider-side queues or logs were erased unless you have that provider evidence.

Should a rotated endpoint create a new account record?

Usually it should create a new delivery row linked to the old application record through a short-lived migration reference. Rotation is a routing change, not proof of a new person. Re-check account ownership and purpose, retire the old endpoint, and keep the old value out of analytics.

Can BotBrowser enable Web Push for a profile?

BotBrowser can preserve a declared permission input for an authorized controlled-context review. It does not grant permission, choose a notification setting, create a subscription for a person, or guarantee delivery. The application must still obtain user intent and reconcile its own server state.

Sources

Practical conclusion

Model Web Push as a lifecycle: permission enables a browser capability, a service worker registration owns a subscription, a server record authorizes delivery, and cleanup ends the purpose. Reconcile endpoint rotation, provider failures, permission changes, logout, account closure, and unsubscribe() with explicit ownership and short retention. Keep endpoint and key material out of analytics and identity systems. BotBrowser can make an authorized permission input repeatable for compatibility review, but the application and its push provider remain responsible for user intent, delivery state, and deletion.

#Web Push#Push Api#Service Workers#Permissions#Privacy

This article is part of the guide: Web Platform Surfaces: A Consistency Review Map

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.