E EMBAN / Docs

Node.js Backend

Send events from your Node.js backend and create embed sessions for your frontend.

Key scopes: use an ingest key for /v1/events. Keep an admin key on your trusted backend for dashboard management and /v1/embed-sessions.

Optional: use the server-side SDK

If you prefer not to wire raw fetch calls yourself, use @emban/sdk on the backend for events, queries, and signed embed sessions:

import { EmbanClient } from '@emban/sdk';

const ingestClient = new EmbanClient({
  apiKey: process.env.EMBAN_INGEST_KEY,
  baseUrl: 'https://emban.sidelabs.dev',
});

const adminClient = new EmbanClient({
  apiKey: process.env.EMBAN_ADMIN_KEY,
  baseUrl: 'https://emban.sidelabs.dev',
});

await ingestClient.ingest([
  {
    tenant_id: 'customer_1',
    event_name: 'api.call',
    string_props: { model: 'gpt-4', endpoint: '/chat' },
    numeric_props: { tokens: 150, latency_ms: 230 },
  },
]);

const session = await adminClient.createEmbedSession({
  tenantId: 'customer_1',
  dashboardId: 'dash_abc',
  permissions: {
    allowDrillDown: true,
    allowedDimensions: ['model', 'endpoint'],
  },
});

console.log(session.embedUrl);

The package is server-side only. Browser embedding still uses signed embed sessions plus iframe, the browser helper, or the iframe React wrapper in your frontend app.

Frontend handoff

Once your backend returns embed_url, pass it to your frontend. For React or Next.js, the cleanest client path is @emban/embed-helper/react:

import { EmbanEmbedFrame } from '@emban/embed-helper/react';

export function Analytics({ embedUrl }) {
  return (
    <EmbanEmbedFrame
      embedUrl={embedUrl}
      containerStyle={{ minHeight: 600 }}
    />
  );
}

If your backend rotates the signed session or switches to a different tenant-scoped session, keep the same mounted wrapper and pass the new signed URL down. The helper layer swaps it without tearing the whole wrapper down.

Send events

const EMBAN_INGEST_KEY = process.env.EMBAN_INGEST_KEY;
const EMBAN_ADMIN_KEY = process.env.EMBAN_ADMIN_KEY;
const EMBAN_URL = 'https://emban.sidelabs.dev';

async function trackEvent(tenantId, eventName, props = {}) {
  await fetch(`${EMBAN_URL}/v1/events`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${EMBAN_INGEST_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      events: [{
        tenant_id: tenantId,
        event_name: eventName,
        timestamp: new Date().toISOString(),
        ...props
      }]
    })
  });
}

// Usage
await trackEvent('customer_1', 'api.call', {
  user_id: 'user_42',
  string_props: { model: 'gpt-4', endpoint: '/chat' },
  numeric_props: { tokens: 150, latency_ms: 230 }
});

Batch events

// Buffer events and flush periodically
const buffer = [];

function track(tenantId, eventName, props) {
  buffer.push({ tenant_id: tenantId, event_name: eventName,
    timestamp: new Date().toISOString(), ...props });
}

async function flush() {
  if (buffer.length === 0) return;
  const events = buffer.splice(0, 1000);
  await fetch(`${EMBAN_URL}/v1/events`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${EMBAN_INGEST_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ events })
  });
}

setInterval(flush, 5000); // flush every 5s

Create embed session

async function getEmbedUrl(tenantId, dashboardId) {
  const res = await fetch(`${EMBAN_URL}/v1/embed-sessions`, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${EMBAN_ADMIN_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({
      tenant_id: tenantId,
      dashboard_id: dashboardId,
      expires_in: 3600,
      permissions: {
        allow_drill_down: true,
        allowed_dimensions: ['model', 'endpoint']
      }
    })
  });
  const { embed_url } = await res.json();
  return embed_url;
}

// Express route
app.get('/api/analytics', async (req, res) => {
  const url = await getEmbedUrl(req.user.orgId, 'dash_abc');
  res.json({ embed_url: url });
});
Validation flow: once Node is minting signed sessions correctly, verify the mounted browser behavior in Embed Runtime, then use the React guide or Next.js guide for the client-side handoff.