Skip to content

Notifications

Overview

The Notification service provides real-time WebSocket connections for receiving notifications about events in your workspace. Get instant updates about data changes, function executions, and system events.

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:

// WebSocket URL format
wss://ws.centrali.io/?token=YOUR_JWT_TOKEN&workspaceSlug=YOUR_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 compute functions
  • Application-specific events

Sending Notifications from Functions

While WebSocket connections are for receiving notifications, compute functions can trigger notifications that are delivered via WebSocket to connected users:

async function run() {
  const orderId = executionParams.orderId;

  // Process something
  const order = await api.fetchRecord(orderId);

  // Your processing logic here
  api.log({ message: 'Order processed', orderId });

  return { success: true, orderId };
}

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) — never ws:// in production
  • Store JWT tokens securely and refresh before expiry
  • Handle close code 4003 by 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 workspaceSlug is correct
  • Check if connection closed with code 4001 (invalid token) or 4002 (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