Skip to main content

Subscriptions from your backend

Your backend has two jobs in the subscription flow, and both are small. It authorises the widget — one hash per person, per resource — and it notifies a group when something happens to the thing they follow. Everything else happens in the browser, in your own interface. This page is for developers. The concept it implements is described in subscriptions; the browser half is the subscribe widget.
Closed beta. Embedding the subscribe widget in your own app is enabled per organisation and is off by default. The methods below ship in the released SDKs, but the surface they drive is not open to every organisation yet — ask your Notifizz contact to switch it on.

TL;DR

  • Two widget authentication modes, chosen per environment: secure (the default) requires a server-computed hash for every subscriber-and-resource pair, public trusts the front API key alone.
  • generateSubscribeToken(subscriberId, resourceId) mints that hash. It is a local HMAC computation — no network call, no rate limit, safe to compute per render.
  • The hash binds one person to one resource. It is not a session, it does not expire, and it authorises nothing else.
  • notifySubscribers(resourceId, properties) is a thin wrapper over track(): it emits the event subscription-entered with resourceId merged into the properties.
  • Any event works. notifySubscribers() is a shortcut, not a requirement — your own event carrying the group id does the same job.
  • In public mode the subscriber list is returned without email addresses. Only secure mode may see them.

The two authentication modes

The widget talks to Notifizz from the browser, so it can only carry secrets that are safe to ship in a bundle. The mode decides what proof it must carry on top of the front API key. It is configured per environment, so your dev environment can be permissive while production is not.

Why secure is the default

The front API key is public by design — it is embedded in your frontend bundle and visible to anyone who opens the network tab. In public mode it is the only credential, so the browser is free to name any subscriber id it likes: subscribe somebody else to a resource, unsubscribe them from one, or read who else is subscribed. In secure mode the request must also carry a hash your backend computed with a secret the browser never sees. Notifizz recomputes it and compares the two in constant time; a mismatch is refused before anything is read or written. And because the hash proves the subscriber id, the server uses the proven id and ignores the one in the request body — a tampered body changes nothing. That last property is why public mode also refuses to return email addresses. If any browser can pose as any subscriber, the subscriber list must not be a way to harvest addresses.

generateSubscribeToken

Mints the hash for one subscriber and one resource:
Available in all three backend SDKs, with the same argument order and the same output.
It is a pure local computation: no HTTP call, no quota, no failure mode other than a wrong key. Compute it wherever you already know both ids — in the handler that renders the page, or in the endpoint that returns the resource.

Getting the hash to the browser

Whatever you already do for authenticated data works. Two shapes cover almost everything:
  • Server-rendered page — compute the hash alongside the resource and pass it into the template. The widget receives it as a prop or a data- attribute.
  • Single-page app — return the hash on the object your frontend already fetches. A list of projects becomes a list of { id, name, subscribeHash }.
Node.js — hash alongside the resource
Three properties are worth internalising before you design around it:
  • One hash per pair. A hash minted for proj_4a19f is refused on proj_77c02. A page showing twenty cards needs twenty hashes.
  • It never expires. It is a binding, not a session. Revoking someone’s access to a resource in your product does not invalidate a hash you already handed them — which is fine, because the hash only ever authorises following that one resource and reading who else follows it.
  • authSecretKey never leaves your backend. It is the same secret behind generateHashedToken for notification-center auth; see API keys. Shipping it to the browser would make secure mode exactly as strong as public mode.
The subscriber id you hash must be byte-identical to the one the widget is initialised with. User_8f21 and user_8f21 produce different hashes, and the widget gets a 401 with no other clue. If subscribing silently does nothing, compare those two strings first.

notifySubscribers

Emits the event that a subscriber campaign listens to:
What it actually does is one line: it merges resourceId into the properties and calls track('subscription-entered', …). Everything true of track() is true here — the same idempotency handling, the same schema modes, the same failure behaviour. Two consequences of it being a plain track():
  • A campaign has to exist on the subscription-entered event, with Subscribers as its recipient. Without one, the event is recorded and nothing is sent — the same as any event nobody listens to.
  • In strict schema mode you must declare the event first. strict refuses to push an undeclared event, and notifySubscribers() does not exempt itself:
Node.js — required in strict mode
In the default soft mode the track goes through and the SDK warns.

You do not have to use it

notifySubscribers() exists so the common case is one call. It is not a privileged path: the campaign resolves the group from whichever event property carries the group id, so your own domain event does the same job and reads better in your codebase.
Node.js — your own event, same result
Configure the campaign on project.deployment_finished with Subscribers as the recipient, and the orchestrator resolves projectId as the group. Prefer this when the event already exists for other reasons — one event, several campaigns, no duplicate. The property may hold a single id or an array of ids. An event naming several groups notifies their union, with anyone in more than one of them counted once. Never put a list of people in the payload — see why.

Replays and backfills

Pass occurredAt when the business event happened at a moment other than the one you are calling from — a replay, an offline batch, a history import. It dates the event itself:
Node.js
A replay notifies today’s members, not the ones from back then. occurredAt records when the event happened; it does not rewind the group. Subscribers are resolved at the moment the campaign runs, so anyone who joined since will receive the replayed notification and anyone who left will not — however old the declared date is.Replaying a long history is therefore rarely what you want on a subscriber group. If the point is to reach the people who were members at that time, resolve that list yourself and target it explicitly.
A value in the future is refused at ingestion, beyond a small tolerance for clock drift. Omit it in the ordinary case.

What the backend SDKs do not do

There is no supported server-side path to create or remove a subscription. Subscriptions come from the subscribe widget, where the person subscribing is the person consenting and the click is the record. A backend that could subscribe people on their behalf would turn that record into an assertion nobody made. Reading who is subscribed is likewise a widget concern: every mounted instance already exposes the list for its resource, shared across the components watching it. If your product genuinely needs an audience you assemble yourself rather than one people opt into, that is a different mechanism — resolve it with an enricher, or launch a broadcast against a CRM segment.

Failure modes

FAQ

Yes, and existing subscriptions are unaffected — the mode governs how requests are authorised, not what is stored. What breaks is any page still mounting the widget without a hash: it starts refusing to mount. Ship the hash first, flip the mode second.
There is nothing to cache. It is one HMAC over two short strings, with no network call — recomputing it per render costs less than looking it up. Do not store it anywhere durable either: a hash you persist is a hash you have to invalidate, for no benefit.
It is a capability, not a secret: whoever holds it can follow that one resource as that one subscriber, and read who else follows it. That is exactly what the person it was minted for is allowed to do. Mint it only for the people your own authorisation rules already allow on the resource, and never for a subscriber id other than the caller’s own.
No. The third constructor argument is only needed to expose enrichers. A client that only mints tokens and tracks events needs the auth secret and the SDK secret.
Yes, and that is the normal shape. A group is just an id; any number of campaigns, on any number of events, can resolve the same one. The subscription is a statement about the resource, not about a single kind of message.
The one your target channel needs. For the notification center it must match the identity your widget authenticates with, exactly. For email it should be resolvable to an address — either it is the address, or an enricher maps it. See reaching a subscriber on a channel.

See also

Subscribe widget

The browser half — React, Vanilla, and the script tag.

Subscriptions

What a subscription is and how it becomes a notification.

API keys

Which secret goes where, and which one signs this hash.

Event tracking

track(), schema modes and idempotency — all of which apply here.