Node.js Backend
Send events from your Node.js backend and create embed sessions for your frontend.
ingest key for /v1/events. Keep an admin key on your trusted backend for dashboard management and /v1/embed-sessions.These four links are the shortest path through the backend side of the Emban integration flow.
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 });
});