Skip to main content

Web Push (VAPID) Configuration

UTM delivers browser push notifications using the Web Push protocol. The server signs every push with a VAPID (Voluntary Application Server Identification) key pair, and browsers tie each push subscription to the public half of the pair. The keys are managed from the admin Settings page so an operator can rotate them without a redeploy.

This page covers how to generate a new key pair, how to enter the values in the admin UI, what changes when you rotate the public key, and how the test push button works.

Where the keys live

Three values drive Web Push delivery. They are stored in the SystemConfig table; the private key is encrypted at rest.

KeyTypeDefaultPurpose
web_push.publicKeystringemptyURL-safe base64 VAPID public key. Sent to the browser to create push subscriptions.
web_push.privateKeystringempty (encrypted)URL-safe base64 VAPID private key. Used by the API to sign each push. Never sent to the browser.
web_push.subjectstringmailto:support@universaltrademanager.comOperator contact URL sent on every push. Must start with mailto:.

Until both web_push.publicKey and web_push.privateKey are set, GET /api/v1/push-subscriptions/vapid-public-key returns 503 and the push delivery worker logs a warning and returns without sending anything. Email, in-app, and other notification channels continue working.

Generate a key pair

Use the web-push CLI from your local machine, the API container, or anywhere with Node available:

npx web-push generate-vapid-keys

The tool prints a public and a private key. Copy both values; you will paste them into the admin UI in the next step. Treat the private key the same way you treat any cryptographic secret.

Enter the values in the admin UI

  1. Sign in as an admin and open Settings.
  2. Scroll to the Web Push (VAPID) card.
  3. Paste the public key, the private key, and the subject. The subject must start with mailto:.
  4. Click Save Settings.

The private-key input is masked and is never pre-filled on reload. Leave it blank when you only want to change the subject or rotate the public key.

Send a test push

Once the configuration is saved and at least one browser has subscribed under your admin user, the Send Test Push button is enabled. The test dispatches a small payload to every subscription registered under your account and lets you confirm the end-to-end path works.

Subscribe a browser from the Devices page before using the button. If no subscriptions exist for your admin user, the endpoint responds 503 with a clear message so the UI can prompt you to subscribe.

Rotating the key pair

Browser push subscriptions are tied to the public key. Rotating the public key invalidates every existing subscription, and each affected device must re-subscribe through the UI. Plan a rotation when:

  • The private key is suspected of being leaked.
  • You are migrating away from a vendor-managed key pair to your own.
  • A key was generated with an outdated curve and you want to refresh.

Rotation steps:

  1. Generate a new pair with npx web-push generate-vapid-keys.
  2. Save the new public, private, and subject values in the admin UI.
  3. Communicate the change to subscribed users; they must visit the Devices page in a browser to re-subscribe.
  4. Send a test push from your own browser as a smoke check.

Migrating from environment variables

Before this surface existed, the keys lived in the VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, and VAPID_SUBJECT environment variables. Existing deployments can copy their current values into SystemConfig in one shot with the migration script:

pnpm --filter @utm/api exec tsx scripts/migrate-vapid-env-to-config.ts

The script reads the three env vars, refuses to overwrite an existing web_push.publicKey row, and is safe to re-run. Once you have verified the rows landed correctly and a test push succeeds, remove the three VAPID_* variables from Railway environment configuration.

Reference

  • Admin endpoints: GET /api/admin/config/web-push, PUT /api/admin/config/web-push, POST /api/admin/config/web-push/test.
  • Public endpoint: GET /api/v1/push-subscriptions/vapid-public-key.
  • Service entry points: getWebPushConfig and updateWebPushConfig in apps/api/src/services/admin/system-config.service.ts.