Skip to content

Identity verification

Identity verification: userHash = HMAC-SHA256 of the externalId with the website's secret key; /api/v1/identity.

Stops someone pretending to be one of your users to get their notifications.

The browser SDK tells relaybell who a visitor is with externalId (your own user id, often an email). That call is public, so without a check anyone could send your customer's externalId from their own browser and start receiving that customer's notifications — cart reminders, order updates and anything else you send to that id. With identity verification, your server signs each externalId with a secret key that only you and relaybell know, and relaybell only accepts an externalId whose signature is correct.

Turn it on: in the dashboard, Settings → Developers → User ID verification shows your secret key and a Require verified User IDs switch. Start sending userHash first, then switch it on. The WordPress plugin (1.2.4 or newer) does all of this automatically, and the Shopify code on the dashboard's Install page includes the signature.

The signature (userHash)

userHash = lowercase hex HMAC-SHA256, where the key is your secret key — the 64-character hex string, used as plain text (don't hex-decode it) — and the message is the externalId exactly as you send it (numbers as their text form, e.g. 42 → "42"). relaybell compares it without regard to upper/lower case. Make it on your server — never put the secret key in code that runs in the browser.

// Node.js
const crypto = require("crypto");
const userHash = crypto.createHmac("sha256", IDENTITY_SECRET).update(String(externalId)).digest("hex");
// or, with the Node SDK: const { userHash } = require("relaybell"); userHash(IDENTITY_SECRET, externalId)

On Shopify, Liquid runs on Shopify's servers, so the key can go in your theme without reaching the page:

Liquid
{{ customer.email | hmac_sha256: 'YOUR_SECRET_KEY' }}
JavaScript
RelayBell.init({
  projectId: "YOUR_PROJECT_ID",
  apiBase: "https://relaybell.com",
  externalId: "jane@example.com",
  userHash: "<signature made on your server>"
});

// or later, e.g. once the visitor signs in:
RelayBell.identify("jane@example.com", "<signature made on your server>");

The SDK sends userHash wherever it sends externalId. If relaybell answers that the id wasn't verified, the SDK logs one console warning: relaybell: the User ID was not verified — check userHash.

What relaybell does with it

RequestResult
Valid userHashThe externalId is used. Response identity: "verified".
Missing or wrong userHash while verification is required, or a wrong userHash at any timeThe externalId is ignored for that request: the browser still subscribes (201/200 as usual) without that id — it isn't linked to the subscriber who has that externalId, no stored user id is set or changed, and that subscriber's earlier opt-out isn't cleared (a browser relaybell already knows stays with its own subscriber). Response identity: "unverified".
No userHash while verification is not required (the default)The externalId is accepted as before. Response identity: "unchecked".

This applies to the public routes the browser SDK uses: POST /api/v1/projects/:id/subscriptions (body fields externalId, userHash; the identity field is left out when no externalId was sent) and POST /api/v1/projects/:id/events (an event's user id is stored only when its userHash is valid, or when no hash is sent and verification isn't required). Calls made with your API key — such as POST /api/v1/events from your server — are already trusted and don't need userHash.

Managing the secret key

RouteDescription
GET /api/v1/identityReturns { "secret": "…", "required": false }. Creates the secret key on the first call. API key or dashboard session.
PUT /api/v1/identityBody { "required": true } turns the requirement on (and creates the secret key if there isn't one yet); false turns it off. Returns { "required": true }. Anything other than true/false answers 400 invalid_required. Turning it on works with an API key or the dashboard session; turning it off only from the dashboard — with an API key, { "required": false } answers 403 identity_off_requires_dashboard, so a leaked key (a WordPress site's, say) can't switch the protection off.
POST /api/v1/identity/rotateMakes a new secret key and returns { "secret": "…" }. The old key stops working at once, so update your site straight away. Dashboard session only — an API key gets 403 session_required, so a leaked API key can't swap your secret.
Shell
curl https://relaybell.com/api/v1/identity -H "Authorization: Bearer rb_live_xxxxx"

curl -X PUT https://relaybell.com/api/v1/identity \
  -H "Authorization: Bearer rb_live_xxxxx" -H "Content-Type: application/json" \
  -d '{ "required": true }'

Note

The secret key is never included in /config, webhooks or any other response — only in /api/v1/identity. The identityVerification value in GET /api/v1/settings is read-only: PUT /api/v1/settings ignores it, so use PUT /api/v1/identity.