Use the publishable web key in browser code, and rotate your app token with a grace period for the old one.
Two credentials
| App token | Publishable web key | |
|---|---|---|
| Looks like | 22 alphanumeric characters | nnweb_… |
| Can do | Everything the app can: send, read inboxes, manage groups, read analytics | Register or deregister one browser device, and read or mark that device's own inbox. Reads the app's public VAPID key. |
| Can't do | — | Send, list groups or subscribers, read another device's data, or touch Classic or account routes |
| Safe in page source | No, it can send to everyone | Yes |
Both go in the same place: wherever an endpoint takes appToken (the :appToken path parameter on GETs, the appToken body field on POSTs). The service tells them apart by the nnweb_ prefix. A web key used outside the list below answers 403 web_key_not_allowed. The app token keeps working everywhere.
Use the web key in every browser snippet, and keep the app token on your server and in native app builds.
What a web key may call
| Call | Endpoint |
|---|---|
| Register a web device | POST /api/universal/device/register with { appId, appToken: <webKey>, platform: "web", deviceId, webPush } |
| Deregister it | POST /api/universal/device/deregister with { appId, appToken: <webKey>, deviceId } |
| List, unread count | GET /api/universal/inbox/:appId/:webKey?deviceId=… and …/unread-count?deviceId=… |
| Mark, delete | POST /api/universal/inbox/read, read-all, delete or clear with { appId, appToken: <webKey>, deviceId, entryId? } |
| Public VAPID key | GET /api/universal/web-push/keys/:appId/:webKey |
A web key reaches only these routes, and only devices it registered itself. A device registered with the app token, or on iOS or Android, is out of reach even if its deviceId is known (403 web_key_scope). A request on another app never matches (401, like a wrong token).
Read the web key
GET https://app.nativenotify.com/api/apps/<APP_ID>/web-key
This needs a dashboard session or MCP token; an app token is refused on /api/apps/*. Like the VAPID keypair, the key is created the first time you read it.
{
"ok": true,
"app": { "id": 123, "name": "Corner Bakery" },
"webKey": "nnweb_1f1c…",
"webKeyCreatedAt": "2026-09-29T12:00:00.000Z",
"scope": { "canRegister": true, "canReadOwnDeviceInbox": true, "canSend": false },
"webPush": { "tokenType": "web", "registerPath": "/api/universal/device/register", "maxPayloadBytes": 3993 }
}
In the dashboard, copy or regenerate it under App Settings → App keys → Publishable web key. The MCP tool is get_app_web_key.
Rotate the web key
POST https://app.nativenotify.com/api/apps/<APP_ID>/web-key/rotate { "confirm": true }
The old key stops authenticating immediately, with no grace window. Owner or admin only; without "confirm": true the call answers confirmation_required. The MCP tool is rotate_app_web_key.
Sites set up by Agent Notify
After rotating, update NN_WEB_KEY in native-notify/web/nativeNotifyIds.js, where Agent Notify puts the key, or ask Agent Notify to, and redeploy, or visitors can't register.
Rotate the app token
Rotate after a leak. You choose how long the old token keeps working, so your own integration doesn't break.
GET /api/apps/<APP_ID>/app-token
POST /api/apps/<APP_ID>/app-token/rotate { "confirm": true, "graceDays": 7 }
POST /api/apps/<APP_ID>/app-token/retire-previous { "confirm": true }
- Rotate issues a new 22-character app token, valid immediately, and keeps the old one valid for
graceDays(0–90, default 7;0retires it at once). It returns the new token andpreviousToken: { activeUntil, graceDays }, never the old value. - Get returns the current token and
rotation: { previousTokenActive, previousTokenExpiresAt }. - Retire ends the grace window early. It's idempotent (
retired: falsewhen nothing was waiting). After the window, or a retire, the old token answers401like any unknown pair. - A second rotation replaces the grace. Only one previous token exists, so the older one stops at once.
- Every mutation is audit-logged and owner or admin only (
403 admin_onlyfor adeveloperteammate).
The MCP tools are get_app_token_status, rotate_app_token and retire_previous_app_token. In the dashboard, use App Settings → App keys → App token.
Data tied to the token (devices, subscribers, Classic inboxes, scheduled pushes) follows the app to the new token. Update whatever authenticates with the old token, such as your app binary, server config or .env, before the grace window ends. Rotating leaves the web key, your devices and your inbox history alone.