Skip to content

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:

// 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 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) — 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