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.
| Key | Type | Default | Purpose |
|---|---|---|---|
web_push.publicKey | string | empty | URL-safe base64 VAPID public key. Sent to the browser to create push subscriptions. |
web_push.privateKey | string | empty (encrypted) | URL-safe base64 VAPID private key. Used by the API to sign each push. Never sent to the browser. |
web_push.subject | string | mailto:support@universaltrademanager.com | Operator 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
- Sign in as an admin and open Settings.
- Scroll to the Web Push (VAPID) card.
- Paste the public key, the private key, and the subject. The subject
must start with
mailto:. - 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:
- Generate a new pair with
npx web-push generate-vapid-keys. - Save the new public, private, and subject values in the admin UI.
- Communicate the change to subscribed users; they must visit the Devices page in a browser to re-subscribe.
- 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:
getWebPushConfigandupdateWebPushConfiginapps/api/src/services/admin/system-config.service.ts.