Web push Migration

Rotating your VAPID keys unsubscribes every web push user

A browser push subscription is cryptographically bound to the key pair that created it. Change the keys and every existing subscriber goes silent — no error, no warning, no way back.

· 3 min read · Notibase

There is one mistake in web push that cannot be undone, and it takes about four seconds to make: clicking the button that regenerates your VAPID keys.

Every browser push subscription you have ever collected stops working immediately. Not degrades. Stops. And because push failures are asynchronous and per-device, most teams find out days later, when someone asks why web engagement fell off a cliff on a Tuesday.

Why it happens

When your page calls pushManager.subscribe(), it passes an applicationServerKey — the public half of your VAPID key pair:

const sub = await registration.pushManager.subscribe({
  userVisibleOnly: true,
  applicationServerKey: urlBase64ToUint8Array(publicKey),
});

The push service — Mozilla's, Google's, Apple's, whoever owns the endpoint — records a hash of that key against the subscription. Not against your domain. Against that one subscription.

Later, when you send, you sign a JWT with the private half and attach the public half. The push service recomputes the hash and compares. If it does not match the one stored at subscribe time, you get:

403 Forbidden
{"code":403,"errno":901,"error":"Forbidden",
 "message":"Request did not validate invalid VAPID public key: VapidPkHashMismatch"}

That is the entire mechanism. The binding is per-subscription and permanent, and it is deliberate: it is what stops anyone who learns your endpoint URL from pushing notifications to your users.

What it looks like in a delivery log

In Notibase this arrives as address_mismatch, which we keep separate from address_gone precisely because the two mean opposite things about whose fault it is:

CodeWhat happenedCan the user come back?
address_goneSubscription expired or the user cleared site dataOnly if they resubscribe naturally
address_mismatchThe subscription was created under different keysOnly by resubscribing, and only after you stop rotating

If your delivery log shows a single subscriber failing with a mismatch, that person cleared site data and re-subscribed against a stale cached key. If it shows all of them, someone rotated your keys.

There is no recovery, and you should be suspicious of anyone who says otherwise

You cannot re-sign an old subscription with a new key. You cannot ask the push service to re-bind. The old private key is the only thing in the universe that can push to those endpoints, and if you no longer have it, those endpoints are inert URLs.

The only real remedy is to rebuild the audience: users have to visit the site again and grant permission again, under the new key. For a site with daily traffic that recovers over a few weeks. For a site people visit twice a year, it does not meaningfully recover at all.

So: back up the private key

Treat the VAPID private key the way you treat a database credential, with one extra property — a database can be restored from a backup, and a VAPID key cannot be regenerated into equivalence. It is not a rotatable secret. It is an identity.

Concretely:

  • Store the private key in your secrets manager the day you generate it, even though your push provider also holds it. Providers get switched. Accounts get closed. Trials expire.
  • Never generate a second key pair for staging on the same origin as production. Origins are what subscriptions are scoped to, and the second key will start invalidating the first one's subscribers as soon as the two share a domain.
  • If your provider's console has a "regenerate" button, know exactly what it does before anyone on your team is ever tempted to click it during an incident.

The migration consequence nobody mentions

This is also the reason web push subscribers do not port cleanly between providers. Your iOS and Android tokens can move — those are minted by Apple and Google against your app, not against your vendor. Web subscriptions are bound to whatever VAPID key your old vendor used, and if the vendor generated it and will not export the private half, the subscriptions cannot come with you.

Before you commit to any migration plan that assumes your web audience travels: open a support ticket with your current provider and ask, in writing, whether you can export the VAPID private key. The answer determines whether your web migration is an import or a rebuild.

Notibase lets you import an existing VAPID key pair rather than forcing a new one, for exactly this reason — see Importing an audience. That does not make us generous, it makes us aware that a subscription list you cannot take with you is a subscription list somebody else owns.