E EMBAN / Docs

Tenants

Emban is built for B2B SaaS. Every event has a tenant_id that represents one of your customers. When you create a signed embed session for a published dashboard, you specify which tenant's data to show.

How it works

  1. Ingest: Send events with tenant_id set to your customer's identifier
  2. Query: All queries are automatically filtered by the tenant specified in the embed session
  3. Embed: Each signed session opens the published dashboard only for that tenant — customers never see each other's data

Choosing a tenant_id

Use whatever identifier your system already uses for customers:

Keep tenant_id consistent across all events. It's the primary isolation boundary.

Data isolation

Emban enforces tenant isolation at the query level. The embed JWT token contains the tenant_id, and every ClickHouse query includes a WHERE tenant_id = ? clause. There is no way for a tenant to access another tenant's data through the embed.

Why this matters: you do not need to build a separate dashboard per customer. One published dashboard can be embedded for many customers, while each session stays scoped to its own tenant.

Common multi-tenant patterns

Most customer-facing setups reuse one published surface and let the signed session provide the customer boundary.

Pattern 1 One published dashboard, many tenants Publish one dashboard definition and mint a different signed session per customer. This is the default Emban path and the main reason tenant scope belongs in the session, not in the dashboard itself.
Pattern 2 Workspace ID as tenant_id Use the same stable customer identifier your product already trusts for billing, auth, and routing. Reusing that ID keeps the analytics surface aligned with the rest of the product.
Pattern 3 Rotate sessions, not dashboards When the host app switches from one customer context to another, mint a fresh signed session or swap the embed URL. Do not rebuild the dashboard definition just to change tenant scope.
Pattern 4 Permissions refine, tenants isolate Use permissions for locked filters, hidden widgets, and drill bounds only after the tenant boundary is already correct. Permissions narrow a surface; they do not create a trust boundary.
Pattern 5 Keep drafts tenant-agnostic Build and publish dashboards as reusable surfaces. The customer-specific state belongs to the signed session, so the same published snapshot can stay portable across tenants.

Anti-patterns

Example: creating a signed tenant-scoped session

// Your backend
const session = await fetch('https://emban.sidelabs.dev/v1/embed-sessions', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ADMIN_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    tenant_id: currentUser.orgId,  // your customer's ID
    dashboard_id: 'dash_abc123',
    expires_in: 3600
  })
});

const { embed_url } = await session.json();
// Pass embed_url to your frontend iframe or browser helper
Validation path: once the tenant boundary is clear, move to Embed Runtime to drive the signed session from the browser, then to Permissions if each customer-facing surface needs tighter policy bounds.