WebSocket API Integration

Overview

This system uses a native WebSocket connection over WSS to stream real-time asset telemetry from the IoT Pro backend.

  • Socket URL: wss://iotpro.iotsolutions.com.mt/api/ws


Authentication is handled via a JWT token, which is required for both REST API access and WebSocket subscription.

  • Token TTL: ~2.5 hours

  • Refresh strategy: token is refreshed 5 minutes before expiry


Connection Flow

The client (iotpro-socket-client.js) available in Resources (at the bottom of this page) follows this sequence:

  1. Authenticate with backend and obtain JWT

    1. A dedicated account (provisioned for WebSocket connections) is used

  2. Start JWT refresh scheduler

  3. Fetch all assets that the authenticated user can read

    1. A map of asset identifiers to asset names is created here

  4. Fetch additional asset details based on asset profiles

  5. Open a WebSocket, authenticate, and subscribe to all assets

  6. Parse and handle incoming telemetry data


API Calls

Authentication

Log In

A JWT token is obtained using the login endpoint:

const response = await fetch(`https://iotpro.iotsolutions.com.mt/api/auth/login`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json"
    },
    body: JSON.stringify({
      username: IOTPRO_USERNAME,
      password: IOTPRO_PASSWORD
    })
  }
);

The returned JWT is cached and used for subsequent API calls and WebSocket authentication.


Refresh Token

The token is refreshed using this endpoint:

const response = await fetch(`https://iotpro.iotsolutions.com.mt/api/auth/token`, {
    method: "POST",
    headers: {
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        refreshToken: iotproRefreshToken
    })
});


Assets

Fetching Assets

The following endpoint is used to fetch all assets using pagination.

const res = await fetch(`https://iotpro.iotsolutions.com.mt/api/user/assets?page=${page}&pageSize=${PAGE_SIZE}`, {
    method: "GET",
    headers: {
      "X-Authorization": `Bearer ${iotproJwt}`
    }
  }
);


Assets are mapped into a local lookup table for fast resolution during streaming.

const data = await res.json();

for (const asset of data.data) {
  const id = asset.id?.id;
  const type = asset.type;

  if (!id || !type) continue;

  assets[id] = {
    name: asset.name,
    type
  };
}


Asset Details

Additional asset metadata is retrieved via telemetry attributes:

const res = await fetch(`https://iotpro.iotsolutions.com.mt/api/plugins/telemetry/ASSET/${uuid}/values/attributes/SERVER_SCOPE`, {
    method: "GET",
    headers: {
      "X-Authorization": `Bearer ${iotproJwt}`
    }
  }
);


Asset details can then be parsed as follows:

const data = await res.json();
const assetDetails = data.find(attr => attr.key === "assetDetails")?.value;


WebSocket Connection

Creating a Connection

const socketUrl = "wss://iotpro.iotsolutions.com.mt/api/ws";
const ws = new WebSocket(socketUrl);


Authentication and Subscription

ws.on("open", () => {
  const msg = {
    authCmd: {
      cmdId: 0,
      token: jwt
    },
    // NOTE commands are created according to the number of assets and PAGE_SIZE
    cmds: [
      {
        cmdId: 1,
        type: "ENTITY_DATA",
        query: {
          entityFilter: {
            type: "entityType",
            entityType: "ASSET"
          },
          pageLink: {
            pageSize: 1000,
            page: 0
          },
          entityFields: [
            { type: "ENTITY_FIELD", key: "name" }
          ],
          latestValues: []
        },
        latestCmd: {
          keys: [
            // NOTE add relevant keys for required asset profiles
            { type: "TIME_SERIES", key: "device_id" },
            { type: "TIME_SERIES", key: "telemetry_param_latitude" },
            { type: "TIME_SERIES", key: "telemetry_param_longitude" }
          ]
        }
      }
    ]
  };

  ws.send(JSON.stringify(msg));
});


Incoming Message Handling

Incoming WebSocket messages contain telemetry updates for subscribed assets. Each message is associated to a specific subscription via cmdId, which identifies the originating command in the initial WebSocket request.

All incoming messages follow the structure below:

{
  "cmdId": 1,
  "update": [
    {
      "entityId": { "id": "..." },
      "latest": { "TIME_SERIES": { ... } }
    }
  ]
}
  • cmdId: Identifier of the subscription command

  • update: Array of entity updates returned for the subscription

  • entityId.id: Unique asset identifier

  • latest.TIME_SERIES: Latest telemetry values for the asset


Incoming messages are parsed and mapped to cached asset metadata.

ws.on("message", (raw) => {
  const msg = JSON.parse(raw.toString());

  const updates = [];

  for (const update of msg.update) {
    const id = update.entityId?.id;

    if (!id || !assetsLookup[id]) continue;

    const timeSeries = update.latest?.TIME_SERIES;

    if (!timeSeries || Object.keys(timeSeries).length === 0) continue;

    updates.push({
      name: assetsLookup[id].name,
      profile: assetsLookup[id].type,
      data: timeSeries
    });
  }

  return updates;
});


Connection Management

This section describes how authentication and WebSocket stability are maintained during runtime.

Token Refresh

JWT tokens are refreshed before expiry to maintain session validity.

const expiry = getJwtExpiry(iotproJwt);
const refreshInMs = Math.max(expiry - Date.now() - 5 * 60 * 1000, 1000);

tokenRefreshTimer = setTimeout(async () => {
    try {
        await refreshToken();
    } catch {
        await login();
    }

    startTokenRefreshTimer();
}, refreshInMs);


WebSocket Reconnection

On a socket closed event, the client performs a throttled reconnection and ensures authentication is valid.

setTimeout(async () => {
    try {
        await refreshToken();
    } catch {
        await login();
    }

    startSocket();
}, 5000);


Resources

iotpro-socket-client.js