Notifications 2.0 vs Realtime for websockets issu

Hi all, I’m a tad confused when it comes to the difference between the Notifications 2.0 & Realtime.

I would like to use mqtt / websockets (not long polling) inside a c# / node application that listens to all events, alarms, telemetry, location changes to our device on c8y production instance please. This will then update our Redis cache with that data ready for our React frontend to subscribe too via SignalR

I’ve tried the c# websocket route but I keep getting a 403 errors, even though I have the highest level roles attached to my user:
[{“channel”:“/meta/handshake”,“error”:“403::Handshake denied”,“advice”:{“reconnect”:“none”},“successful”:false}]

I tried the node examples provided in the test-microservice repo (github/SoftwareAG/cumulocity-examples/blob/ad3670210071553f6c1a6c5d3cc097dec1f2ff2b/microservices/node-microservice/controllers.js#L100), I created the following code but I get a client.getFetchOptions is not a function error:


// "@c8y/client": "^1020.29.0",
// "tslib": "^2.8.0"

const { Client, BasicAuth, Realtime } = require('@c8y/client');

// Authenticate with BasicAuth
const auth = new BasicAuth({ 
  user: 'USRNAME',
  password: 'PASWRD',
  tenant: 'TEANANT'
});

const baseUrl = 'https://TEANANT.machines.cloud';
const client = new Client(auth, baseUrl);

// Set up the Realtime client
const realtime = new Realtime(client);

(async () => {
  try {
    // Verify authentication by listing inventory (optional)
    const { data } = await client.inventory.list({ pageSize: 100 });
   // console.log('Inventory data:', data);

    // Subscribe to real-time notifications for device alarms

    // Subscribe to alarms
    realtime.subscribe(`/alarms/`, (response) => {
      console.log('Received alarm notification:', response);
    });

    // Subscribe to events
    realtime.subscribe(`/events/`, (response) => {
      console.log('Received event notification:', response);
    });

    console.log('Subscribed to real-time notifications for device:', deviceId);

  } catch (error) {
    console.error("Error during authentication or subscription:", error);
  }
})();


Also, it mentions the role: ROLE_NOTIFICATION_2_ADMIN in the docs but my tenant admin says there is no ROLE_NOTIFICATION_2_ADMIN role.

Really not sure sure where to go from here. I can post the c# code too if required.

Thanks

c# code:

using Newtonsoft.Json;
using System.Net.WebSockets;
using System.Text;

namespace Notification.Api.Managers
{
    public class CumulocityWebSocketService
    {
        private readonly ILogger<CumulocityWebSocketService> _logger;

        public CumulocityWebSocketService(ILogger<CumulocityWebSocketService> logger)
        {
            _logger = logger;
        }

        public async Task ConnectToCumulocityAsync()
        {
            string websocketUri = "wss://tenant.machines.cloud/notification/realtime";
            string username = "USERNAME";
            string password = "PASSWORD";

            await StartWebSocketConnectionAsync(websocketUri, username, password);
        }

        private async Task StartWebSocketConnectionAsync(string uri, string username, string password)
        {
            using var webSocket = new ClientWebSocket();
            var authHeader = GetBasicAuthHeader(username, password);
            webSocket.Options.SetRequestHeader("Authorization", authHeader);

            _logger.LogInformation("Connecting to WebSocket...");
            await webSocket.ConnectAsync(new Uri(uri), CancellationToken.None);

            _logger.LogInformation("WebSocket connection established.");

            // Perform handshake and subscription
            await PerformHandshakeAsync(webSocket);

            await ListenForMessages(webSocket);
        }

        private string GetBasicAuthHeader(string username, string password)
        {
            var authValue = $"{username}:{password}";
            var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(authValue));
            return $"Basic {authHeader}";
        }

        private async Task ListenForMessages(ClientWebSocket webSocket)
        {
            var buffer = new byte[1024 * 4];
            while (webSocket.State == WebSocketState.Open)
            {
                var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    _logger.LogWarning("WebSocket connection closed by server.");
                    await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                }
                else
                {
                    var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    _logger.LogInformation("Received message: " + message);
                }
            }
        }
        private async Task PerformHandshakeAsync(ClientWebSocket webSocket)
        {
            var handshakePayload = new[]
            {
                new {
                    channel = "/meta/handshake",
                    version = "1.0",
                    supportedConnectionTypes = new[] { "websocket" },
                    ext = new {
                        com_cumulocity_authn = new {
                            token = ""   // Replace with actual authentication token
                        }
                    }
                }
            };

            var handshakeJson = JsonConvert.SerializeObject(handshakePayload);
            var bytes = Encoding.UTF8.GetBytes(handshakeJson);
            await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);

            // Receive handshake response
            var buffer = new byte[1024 * 4];
            var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            var response = Encoding.UTF8.GetString(buffer, 0, result.Count);

            // Log the entire handshake response for inspection
            _logger.LogInformation("Raw Handshake Response: {Response}", response);
        }



        private async Task SubscribeToEvents(ClientWebSocket webSocket, string clientId)
        {
            var subscriptionPayload = new[]
            {
                new {
                    channel = "/meta/subscribe",
                    clientId = clientId,
                    subscription = "/events/123466789",  // Example subscription
                    context = "tenant",
                    entities = new[] { "12345" },
                    fragmentType = "c8y_Event"
                }
            };

            var subscriptionJson = JsonConvert.SerializeObject(subscriptionPayload);
            var bytes = Encoding.UTF8.GetBytes(subscriptionJson);
            await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);

            _logger.LogInformation("Subscription sent for events.");
        }


    }
}

Hi Sharry,

things are a little mixed up here.
The realtime API is the “old” API that isn’t very reliable and shouldn’t be implemented in any microservice, while it can be still used for frontend to show data in realtime. This is the one you have unfortunately implemented which you should change.

I’m guessing you are running an own C8Y instance. Which version are you at? Notifications 2.0 must be subscribed to your tenant but the role should always be there beside you are running an old c8y version where it hasn’t been introduced.

For microservice please use only the notification 2.0 API as the “old” API is not reliable and messages could be lost.

For your implementation it seems you are sending the wrong payload… it should only contain

[
  {
    "channel": "/meta/handshake",
    "version": "1.0"
  }
]

https://www.cumulocity.com/api/core/#section/Real-time-operations/Handshake

Thanks Stefan, very confused yep. OK, I’ll go down the Notifications 2.0 route now thanks.

Indeed. Own instance running on machines.cloud.

Where can I find the version we are running & see where Notifications 2.0 is enabled/subscibed to please, I’ll ask my admin (as I’m only a developer).

Correct to say to use the “wss://tenant.machines.cloud/notification2/” as the base url?

Thanks
Sharry

Hi Sharry,

no, you have to go a totally other route for notification 2.0 unfortunately.
Check this: Cumulocity IoT - OpenAPI Specification

and especially this:

You can find the version in the top right clicking on your user. I newer version it is called “Download platform details”. In older versions you can see the version directly in the banner on the right like 1018.x.

1 Like

Using

Backend: 1018.540.195
UI: 1013.0.276

My user has a global role of: Admins

Where do I set the ROLE_NOTIFICATION_2_ADMIN against a user please?

Trying this from the getting started with Notification 2.0 Step by step guide link you sent:
curl --location --request GET "https://X.machines.cloud/notification2/subscriptions" --header "Authorization: Basic BASE64(USER:PASS)_STRING_HERE"

Response:
{"error":"security/Forbidden","message":"Access is denied","info":"https://cumulocity.com/guides/users-guide/getting-started/"}

I resolved it.

Added the ROLE to the user using the path in POSTMAN:
{{baseUrl}}/user/{{TenantId}}/users/{{UserId}}/roles

with the payload:

{ "role": { "self": "https://tenantHere.machines.cloud/user/roles/ROLE_NOTIFICATION_2_ADMIN" } }

That’s the API way, yes. But normally you can do such things by using the Administration AppUsers / Roles

There is no role called/like Notification in my list of roles in the Admin App.

You have either change an existing role or add a new one and assign that permission to that role:

image

1 Like