Skip to main content

Multi-Tenancy with Contexts

Use Contexts to scope notifications by tenant, workspace, organization, environment, or feature area — without duplicating workflows or subscribers. The Inbox displays only notifications whose trigger context exactly matches the Inbox context.
Contexts are a Novu primitive. If you’re new to them, start with the Contexts overview.

How context filtering works

The Inbox uses exact-match filtering. The full context object passed to the <Inbox> must equal (key for key, value for value) the context used at trigger time: This isolation makes context predictable and tamper-resistant (when combined with contextHash).

End-to-end setup

1. Define the tenant context

A tenant context is a JSON object that identifies a tenant. The id is the only required field; data is optional metadata.
You can also pass a bare string for simple cases:
Contexts are auto-created when first seen by Novu (via trigger or Inbox). Existing contexts are not auto-updated to prevent overwriting tenant data. To manage contexts manually, use the dashboard’s Contexts section, the API, or @novu/api.

2. Trigger workflows with context

All notifications emitted by this trigger are isolated to the acme-corp tenant. They will only surface in an Inbox initialized with the same tenant context.

3. Filter the Inbox by tenant

If a subscriber switches tenants in your app, re-render the Inbox with the new context. The Inbox automatically refetches notifications and reconnects the WebSocket scope.

4. Secure the context with contextHash

Because context is set on the client, a hostile user could swap the tenant ID to peek at another tenant’s notifications. To prevent that, generate an HMAC hash of a canonicalized context server-side. Canonicalization is required because JSON objects with the same data but different key order would otherwise produce different hashes. Use a library that implements RFC-8259 canonicalization.
Pass both context and contextHash to the Inbox:
If you change the context you must regenerate the contextHash. The hash is bound to the exact, canonicalized JSON.

Patterns

Switching tenants client-side

Pre-compute one contextHash per tenant on the server:

Per-feature scoping

Contexts aren’t limited to tenants — use them to scope by feature area, environment, or anything else:
The Inbox in your billing dashboard sees only billing notifications; the inbox in your monitoring dashboard sees only monitoring notifications.

Combining tenant + app

The Inbox must declare the same combined context to see these notifications.

Using context data in templates

Once a context is created, its data is exposed in every template editor (email, in-app, SMS, push) via the {{context}} helper. This lets you personalize content per tenant from a single workflow definition. Example In-App body:
You can also branch workflow logic on context. See Contexts in Workflows.

Operational notes

  • Auto-create: contexts referenced for the first time are automatically created in Novu. Visible in the dashboard under Contexts.
  • No auto-update: existing context data is never overwritten by a new trigger or Inbox initialization. To update tenant data, use the API.
  • Context + HMAC: when HMAC is enforced for the project, you must also pass contextHash if context is set. Subscribers will see no notifications otherwise.
  • Order-independent hashing: always canonicalize the JSON before hashing so { a: 1, b: 2 } and { b: 2, a: 1 } produce the same hash.
  • Tenant switch ⇒ re-render: changing context triggers a re-fetch and a new WebSocket subscription scope.

Common pitfalls

  1. Empty context vs no contextcontext={{}} is treated as “no context”. Notifications triggered with a non-empty context will not appear, and vice versa.
  2. Mismatched object shape{ tenant: "acme" } is not equal to { tenant: { id: "acme" } }. Pick one shape and use it consistently in both trigger and Inbox.
  3. Forgetting to regenerate the hash — any change to the context object requires a new contextHash. A stale hash silently drops notifications.
  4. Canonicalization mismatch — if your server canonicalizes but your test fixture doesn’t (or vice versa), hashes diverge. Always go through the same canonicalize step.
  5. Storing secrets in data — context data is read from the client. Don’t include API keys, tokens, or PII you wouldn’t otherwise expose.