Notifications¶
Overview¶
Notifications provide a WebSocket feed for user-facing or system-facing alerts in a workspace. Use this page when you need a live notification channel in the UI or another connected client.
If you are looking for raw record change streams, start with Realtime Quickstart instead. If Centrali needs to notify another system over HTTP, start with Webhooks.
Choose the Right Channel¶
| Need | Start Here | Why |
|---|---|---|
| User or operator alert feed over WebSocket | This page | Notifications are a client-facing live feed |
| Record create/update/delete event stream | Realtime Quickstart | Realtime is the canonical record-event subscription path |
| Server-to-server push delivery | Webhooks | Webhooks send HTTP requests to other systems |
Connection Model¶
- Transport: WebSocket
- Endpoint:
wss://ws.centrali.io/?token=...&workspaceSlug=... - Authentication: JWT token passed in the query string
- Client: Use a native WebSocket client or a standard WebSocket library
This page documents the receiving side of the notification stream. It does not document the producer-side API for creating notifications.
WebSocket Connection¶
Authentication¶
WebSocket connections require a valid JWT token. The token is passed as a query parameter in the connection URL. See the Service Account Authentication Guide for how to obtain a JWT token.
If the token is missing, invalid, or expired, the connection will be closed immediately with one of the following codes:
| Close Code | Meaning |
|---|---|
4001 | Unauthorized — missing or invalid token |
4002 | Missing workspace — workspaceSlug not provided |
4003 | Token expired — obtain a new token and reconnect |
Connection URL¶
Connect to the WebSocket server with your JWT token and workspace:
Native WebSocket (JavaScript)¶
// Create WebSocket connection
const token = 'YOUR_JWT_TOKEN'; // Obtain via Service Account or user login
const workspaceSlug = 'my-workspace';
const ws = new WebSocket(`wss://ws.centrali.io/?token=${encodeURIComponent(token)}&workspaceSlug=${workspaceSlug}`);
// Connection opened
ws.onopen = (event) => {
console.log('Connected to notifications');
};
// Listen for messages
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
console.log('Received notification:', notification);
// Notification structure:
// {
// id: 'notif_123',
// title: 'New Order',
// message: 'Order #12345 has been placed',
// type: 'info',
// read: false
// }
};
// Connection closed
ws.onclose = (event) => {
console.log('Disconnected from notifications', event.code, event.reason);
// Check close code for auth errors
if (event.code === 4001) console.error('Authentication failed');
if (event.code === 4003) console.error('Token expired — obtain a new token');
};
// Handle errors
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
Using with a WebSocket Library¶
// Using the 'ws' library in Node.js
import WebSocket from 'ws';
const token = 'YOUR_JWT_TOKEN';
const workspaceSlug = 'my-workspace';
const ws = new WebSocket(`wss://ws.centrali.io/?token=${encodeURIComponent(token)}&workspaceSlug=${workspaceSlug}`);
ws.on('open', () => {
console.log('Connected to Centrali notifications');
});
ws.on('message', (data) => {
const notification = JSON.parse(data.toString());
console.log('Notification:', notification);
handleNotification(notification);
});
ws.on('close', (code, reason) => {
console.log(`Connection closed: ${code} - ${reason}`);
// Implement reconnection logic if needed
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
function handleNotification(notification) {
switch(notification.type) {
case 'record_created':
console.log('New record created');
break;
case 'function_complete':
console.log('Function execution complete');
break;
case 'alert':
console.log('Alert:', notification.message);
break;
default:
console.log('Notification:', notification.message);
}
}
React Example¶
import { useEffect, useState } from 'react';
function useNotifications(token, workspaceSlug) {
const [notifications, setNotifications] = useState([]);
const [connected, setConnected] = useState(false);
useEffect(() => {
if (!token || !workspaceSlug) return;
const ws = new WebSocket(
`wss://ws.centrali.io/?token=${encodeURIComponent(token)}&workspaceSlug=${workspaceSlug}`
);
ws.onopen = () => {
setConnected(true);
console.log('Connected to notifications');
};
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
setNotifications(prev => [notification, ...prev]);
};
ws.onclose = (event) => {
setConnected(false);
if (event.code === 4003) {
console.log('Token expired — refresh and reconnect');
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Cleanup on unmount
return () => {
ws.close();
};
}, [token, workspaceSlug]);
return { notifications, connected };
}
// Usage in component
function NotificationPanel() {
const token = useAuthToken(); // Your auth token provider
const { notifications, connected } = useNotifications(token, 'my-workspace');
return (
<div>
<div>Status: {connected ? 'Connected' : 'Disconnected'}</div>
{notifications.map(notif => (
<div key={notif.id}>
<h3>{notif.title}</h3>
<p>{notif.message}</p>
</div>
))}
</div>
);
}
Notification Structure¶
Each notification message contains:
interface Notification {
id: string; // Unique notification ID
title: string; // Notification title
message: string; // Detailed message
type: string; // Type: 'info', 'success', 'warning', 'error', 'alert'
read: boolean; // Read status
timestamp?: string; // When the notification was created
metadata?: any; // Additional data specific to the notification type
}
Notification Types¶
System Notifications¶
- Workspace events
- User account updates
- System maintenance alerts
Data Notifications¶
- Record created
- Record updated
- Record deleted
- Bulk operation completed
Function Notifications¶
- Function execution started
- Function execution completed
- Function execution failed
- Function timeout
Custom Notifications¶
- Triggered from functions
- Application-specific events
Receiving vs Producing Notifications¶
This page covers how clients receive the notification stream.
If your workflow starts inside Centrali and needs to:
- react to record changes in code, use Realtime
- notify another service, use Webhooks
- surface alerts in the UI, consume the WebSocket stream documented here
Connection Management¶
Reconnection Strategy¶
When a connection drops, you should reconnect with exponential backoff. If the close code is 4003 (token expired), obtain a new token before reconnecting.
class NotificationManager {
constructor(getToken, workspaceSlug) {
this.getToken = getToken; // Function that returns a valid JWT token
this.workspaceSlug = workspaceSlug;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000; // Start with 1 second
}
async connect() {
const token = await this.getToken();
const url = `wss://ws.centrali.io/?token=${encodeURIComponent(token)}&workspaceSlug=${this.workspaceSlug}`;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
console.log('Connected to notifications');
this.reconnectAttempts = 0;
this.reconnectDelay = 1000;
};
this.ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
this.handleNotification(notification);
};
this.ws.onclose = (event) => {
console.log('Connection closed', event.code);
if (event.code === 4001) {
console.error('Authentication failed — check your token');
return; // Don't reconnect on auth failure
}
// For 4003 (expired) or normal closes, reconnect with a fresh token
this.attemptReconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
setTimeout(() => {
this.connect();
}, this.reconnectDelay);
// Exponential backoff
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000); // Max 30 seconds
}
handleNotification(notification) {
// Your notification handling logic
console.log('Notification received:', notification);
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage
const manager = new NotificationManager(
() => fetchAccessToken(), // Your token provider
'my-workspace'
);
manager.connect();
Best Practices¶
1. Connection Lifecycle¶
- Open connection when user logs in or enters workspace
- Close connection when user logs out or leaves
- Implement reconnection logic for network interruptions
2. Error Handling¶
- Always handle connection errors gracefully
- Provide user feedback when connection is lost
- Queue important actions during disconnection
3. Performance¶
- Avoid keeping connections open when not needed
- Use a single connection per user/workspace pair
- Process notifications efficiently to avoid blocking
4. Security¶
- Never expose sensitive data in notifications
- Always use
wss://(WebSocket Secure) — neverws://in production - Store JWT tokens securely and refresh before expiry
- Handle close code
4003by obtaining a fresh token before reconnecting
Limitations¶
- Maximum 1 connection per user per workspace
- Messages are not persisted - only real-time delivery
- Connection timeout after 5 minutes of inactivity
- Maximum message size: 64KB
Troubleshooting¶
Connection Fails¶
- Verify your JWT token is valid and not expired
- Verify
workspaceSlugis correct - Check if connection closed with code
4001(invalid token) or4002(missing workspace) - Ensure the user has access to the workspace
Not Receiving Notifications¶
- Confirm connection is established (check onopen)
- Verify notification events are being triggered
- Check browser console for errors
Connection Drops Frequently¶
- Implement reconnection logic
- Check for network stability
- Consider using heartbeat/ping messages
Related Documentation¶
- Realtime Quickstart - Record-event subscriptions over SSE
- Realtime Authentication - Token handling for live streams
- Webhooks Guide - HTTP-based event notifications
- Webhooks & Events - Choosing between triggers, webhooks, and live delivery