Skip to content

Notifications: identity verification

A publishable key sits in your page’s code, so anyone can copy it. Without a check, anyone holding it could ask for any user_id’s notifications, or mark them read. Identity verification closes that: your server signs the signed-in user’s id, and Launch answers a browser only for the user id it can prove.

  1. Create the project’s signing secret once, and keep it on your server.
  2. When your server renders a page for a signed-in user, it computes user_hash = HMAC-SHA256(signing secret, user_id) as lowercase hex and hands the page both the user id and the hash.
  3. The page sends user_id with every bell call, and the hash in the X-Nordva-User-Hash header (or a user_hash query parameter, or user_hash in the JSON body of the two PATCH calls).

Launch computes the same HMAC and compares the two in constant time. A missing or wrong hash answers USER_HASH_INVALID (403). The hash never expires: it is the same for a user id as long as the secret is.

Calls with the secret key from your server need no hash.

From the dashboard, or with the secret key:

Terminal window
curl -X POST https://api.nordva.dev/v1/notifications/signing-secret \
-H "Authorization: Bearer nv_live_YOUR_SECRET_KEY"
{ "data": { "signing_secret": "nv_ntf_3f9c...", "created_at": "2026-09-26T10:00:00.000Z", "shown_once": true } }

The secret is shown once, in that answer. Save it where your server keeps secrets (for example NORDVA_NOTIFICATIONS_SECRET). Asking again answers SIGNING_SECRET_ALREADY_SHOWN (409). Never put the secret in the browser or in client-side code: with it, anyone could sign any user id.

The HMAC key is the whole secret exactly as shown, nv_ntf_ included.

Node.js

import { createHmac } from "node:crypto";
export function nordvaUserHash(userId) {
return createHmac("sha256", process.env.NORDVA_NOTIFICATIONS_SECRET).update(userId).digest("hex");
}

Python

import hashlib
import hmac
import os
def nordva_user_hash(user_id: str) -> str:
secret = os.environ["NORDVA_NOTIFICATIONS_SECRET"].encode()
return hmac.new(secret, user_id.encode(), hashlib.sha256).hexdigest()

PHP

<?php
function nordva_user_hash(string $userId): string {
return hash_hmac('sha256', $userId, getenv('NORDVA_NOTIFICATIONS_SECRET'));
}

To check your code: with the secret nv_ntf_example_secret and the user id user_123, the hash is 14a440bc09d5207e006d772d46234831782df0b61b49423e8ec8d6665a92811a.

// userId and userHash came from your server with the page
const res = await fetch(
`https://api.nordva.dev/v1/notifications/inbox?user_id=${encodeURIComponent(userId)}`,
{
headers: {
Authorization: "Bearer nv_pub_live_YOUR_KEY",
"X-Nordva-User-Hash": userHash,
},
},
);
const { data } = await res.json();
// data.notifications, data.unread_count, data.poll_interval_seconds

If the secret may have leaked, rotate it:

Terminal window
curl -X POST https://api.nordva.dev/v1/notifications/signing-secret/rotate \
-H "Authorization: Bearer nv_live_YOUR_SECRET_KEY"

The new secret is shown once, in the answer. The old one keeps working for 24 hours (previous_valid_until), so you can deploy the new one to your server without the bell failing in between. After that only the new one verifies.

Projects that had notifications before 26 Sep 2026

Section titled “Projects that had notifications before 26 Sep 2026”

Before this check existed there was no way to send a hash, so a project that already held notifications keeps working as before: require_user_hash is off, and GET /v1/notifications/unread and PATCH /v1/notifications/{id}/read still answer a browser without a hash. Those answers carry meta.identity_verification: "off" with a warning, and GET /v1/setup-check and GET /v1/notifications/settings warn too. The newer bell endpoints (inbox, unread/count, read-all) always need the hash.

While it is off, anyone with your publishable key can read any user’s notifications. To switch it on:

  1. Create the signing secret and add the hash to your pages as above.
  2. Check a page works with the hash sent.
  3. Switch the check on:
Terminal window
curl -X PATCH https://api.nordva.dev/v1/notifications/settings \
-H "Authorization: Bearer nv_live_YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "require_user_hash": true }'

Every other project has the check on from the start.