Skip to content

Documentation

Core concepts

Pub/Sub

Pub/Sub is short for "Publish/Subscribe", is a messaging style in software design. Imagine it like this: one or more senders (called publishers) share messages on a central topic, and lots of receivers (called subscribers) get and respond to those messages. It's like a way for different parts of a system to talk to each other without knowing who they're talking to. This makes it easier to make big systems that can grow, stay dependable, and be easy to take care of.

The WebSocket protocol

WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection. The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011. The current API specification allowing web applications to use this protocol is known as WebSockets. It is a living standard maintained by the WHATWG and a successor to The WebSocket API from the W3C.

See: https://en.wikipedia.org/wiki/WebSocket

Common uses for WebSockets are:

  • Real-time Communication: WebSockets provide full-duplex communication between client and server, enabling real-time communication in web applications such as online gaming, chat applications, and collaboration tools.
  • Dynamic Data Updates: WebSockets can be used to update dynamic data in real-time without the need for continuous polling. This is useful in applications such as stock tickers, weather updates, and sports scores.
  • Multimedia Streaming: WebSockets can be used to stream multimedia data in real-time, such as audio or video, enabling low-latency communication in applications such as video conferencing and online broadcast platforms.
  • IoT (Internet of Things) Applications: WebSockets can be used to communicate with IoT devices in real-time, allowing for real-time monitoring and control of these devices, as well as sending and receiving data from them.
  • Interactive Dashboards: WebSockets can be used to create dynamic, real-time dashboards that can update in response to user actions or new data. This is useful in fields such as finance, marketing, and business intelligence, where real-time data analysis is critical.
  • Live Multiplayer Gaming Experiences: WebSockets enable live, interactive gameplay for mobile gaming apps by facilitating instant communication between the game server and multiple players.

The WebSocket API

The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

Most browsers and JavaScript runtimes fully support the WebSockets API: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API#browser_compatibility

See: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

JSON Web Tokens

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.

See: https://www.rfc-editor.org/rfc/rfc7519 and https://jwt.io/introduction

JWT implementations exist for many languages and frameworks, including but not limited to: https://jwt.io/libraries

Server

The "server" is the software component that is responsible for managing WebSocket connections, application topics and publications. Each server is highly optimized for low-latency and can handle several thousands of concurrent connections and message flows with minimal impact on hardware resources. Multiple server instances work together as a cluster to increase sockets capacity, throughput and availability.

Application

An "application" is the virtual representation of an isolated and secure namespace for "subscribers", "topics", "messaging" and "publishers". Among other properties, an application defines authentication configuration using JWT, both for WebSocket clients and administrative operations via REST.

Application configuration example:

{
  "enabled": true,
  "appId": "f36b57561fc006e958e521008764a570",
  "name": "My Example App",
  "description": "This is an example application that uses ES512 JWT-based authentication",
  "tier": "free",
  "createdAt": "2023-09-17T15:26:35.666Z",
  "updatedAt": "2023-10-05T13:05:38.238Z",
  "adminKey": {
    "key": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGBy...RzY1gqGo=\n-----END PUBLIC KEY-----",
    "algorithms": [
      "ES512"
    ],
    "issuer": []
  },
  "adminSourceAddress": [
    "0.0.0.0/0"
  ],
  "clientSourceAddress": [
    "0.0.0.0/0"
  ],
  "authKey": {
    "key": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGBy...RzY1gqGo=\n-----END PUBLIC KEY-----",
    "algorithms": [
      "ES512"
    ],
    "issuer": []
  },
  "enableWebSocketInboundTopic": true,
  "amazonSnsForwarder": {},
  "amazonSqsForwarder": {}
  ...
}

Properties:

  • enabled: A boolean value indicating whether the application is enabled or not.
  • appId:A string identifier for the application.
  • name: The name of the application.
  • description: Description of the application.
  • tier: A string that indicates the pricing plan for the application.
  • createdAt: A string that represents the date and time when the application was created.
  • updatedAt: A string that represents the date and time when the application was last updated.
  • authKey: An object that provides the public key and algorithms used for client authentication.
  • adminKey: An object that provides the public key and algorithms used for administrative endpoints authentication.
  • adminSourceAddress: An array containing source IP addresses and CIDR masks that either permit or deny (with a preceding "!") access for administrative connections.
  • clientSourceAddress: An array of source IP addresses and CIDR masks that either allow or deny (preceded by "!") access for client (subscriber) connections.
  • enableWebSocketInboundTopic: A boolean flag used to control the enabling or disabling of topic forwarding for inbound WebSocket messages. When set to TRUE, inbound messages are broadcasted on the secure/inbound topic.
  • amazonSnsForwarder: An object that provides the configuration for forwarding inbound WebSocket messages to an Amazon SNS Topic.
  • amazonSqsForwarder: An object that provides the configuration for forwarding inbound WebSocket messages to an Amazon SQS Queue.

Topics

A topic is an entity that exists within the context of an application, representing isolated namespaces for messages, subscriptions and publications. Each application can hold hundreds of topics, each of them with up to thousands of subscribers.

The following are possible topics categories:

  • Private (prefixed with priv/)
  • Secure (prefixed with secure/)
  • Public

Each WebSocket connection(the client) is ALWAYS subscribed to the following private topics:

  • priv/WEBSOCKET_CONNECTION_ID, for example: priv/f774e704c1eb
  • priv/WEBSOCKET_CONNECTION_JWT_SUBJECT, for example: priv/user0@example.com

    NOTE: A subscription to a private topic is an administrative operation and is forbidden by the client subscription API!

Clients can subscribe or unsubscribe from public topics as needed. Be aware that upon establishing a new connection, clients are automatically subscribed to the main topic.

See also: Subscribing to secure topics below

Subscribers

WebSocket connections that subscribe to one or multiple topics are referred to as "subscribers". A successful subscription to a topic is referred to as a "subscription".

See Pricing/Plan features for subscriptions related quotas

const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)
ws.onmessage = (event) => {
  // Parse incoming WebSocket messages
  const { topic, messageType, data } = JSON.parse(event.data)

  if (topic === 'main' && messageType === 'welcome') {
    // SESSION IS NOW READY, YOU CAN SUBSCRIBE TO APP TOPICS...
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: 'topic1'
      }
    }))
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: 'topic2'
      }
    }))
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: 'topicN'
      }
    }))
  }
}

Connections can also unsubscribe topics on-demand:

ws.send(JSON.stringify({
  type: 'unsubscribe',
  data: {
    topic: 'topic1'
  }
}))
ws.send(JSON.stringify({
  type: 'unsubscribe',
  data: {
    topic: 'topic2'
  }
}))

When clients subscribe to a topic, they'll get a welcome message, and afterwards they'll receive all messages broadcasted to that topic. Let's dive into a detailed example to illustrate this:

const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)

ws.on('message', (event) => {
  // Parse incoming WebSocket messages
  const { topic, messageType, data } = JSON.parse(event.data)
  logger.info(`> received bytes from '${topic}' topic: ${event.data.length}`)

  if (topic === 'main' && messageType === 'welcome') {
    ws.id = data.connection.id
    logger.info(`> connection '${data.connection.id}' established ...`)

    // SUBSCRIBE TO TOPICS HERE
  } else if (messageType === 'welcome') {
    logger.info(`> topic '${topic}' subscribed...`)

    // ON SUBSCRIPTION LOGIC HERE
  } else if (messageType === 'broadcast') {
    logger.info(`> processing message from topic '${topic}'...`)
    logger.info(data)

    // MESSAGE PROCESSING LOGIC HERE
  }
})

Publishers

Publishers are commonly specialized services where application owners have full control of the publication and messaging logic. To do so, the services would trigger HTTP requests to the publication endpoint using a valid authorization token. For a token to be valid, it is required to be verifiable using the application adminKey configuration.
Upon successful operation, these messages can then be received by subscribers who have subscribed to that same topic.

Example event payload received at a subscriber:

ws.on('message', (event) => {
  const { topic, messageType, data } = JSON.parse(event.data)
  /*
    "messageType"   = "broadcast"
    "topic"         = "a-topic-name"
    "data"          = "Hello Pub/Sub!"
  */
}

Optionally see publication example at: https://realtime.21no.de/introduction/#publishing-messages-in-your-apps-topics-rest

Common responsibilities handled by "Publishing Services":

  • Interface between message sources and application topics
  • Handle custom application authentication and authorization strategies
  • Request or issue short-living authorization tokens(JWT) required by the server publication endpoint
  • Validate message payload before broadcasting it to all subscribers in a topic
  • Publish validated messages into application topics

Continue reading at: https://realtime.21no.de/getting-started/#the-subscription-and-publication-workflow

Granting publisher permissions on topics

Granting publishers access to topics is essential for controlling who can publish on them. Publishers, has a Publisher role, and can send messages to allowed topics specified in their configuration.
Let's review an example authorization token (JWT) below, describing a publisher who can publish messages into the main and secure topics:

{
  "roles": [
    "Publisher"
  ],
  "allowedTopics": [
    "main",
    "secure/*"
  ],
  "iat": 1673028921,
  "exp": 1673028926,
  "iss": "https://signer.example.com",
  "sub": "service-name",
  ...
}

Entries in the allowedTopics attribute are matched using the matcher module. Use allowedTopics = ["*"] to allow the publisher to broadcast messages to all topics.

WebSocket Inbound Messaging

You can enable WebSocket clients to emit messages as input to the application. These messages are subsequently routed to enabled forwarding destinations for real-time processing.

ws.send(JSON.stringify({
  type: 'message',
  data: {
    // your message payload, expected type: object or string
    payload: 'hello',
    // message id, auto-generated if not provided
    id: '52',
    // compression, default value: false
    compress: true
  }
}))

Supported messages forwarding destinations:

  • Inbound Topic: Application owners can forward inbound WebSocket messages into the secure/inbound topic.
  • Amazon SNS Topic or SQS Queue: Application owners can forward inbound WebSocket messages to an Amazon SNS Topic or SQS Queue.

Please note that the maximum payload size for a single WebSocket message is limited to 5KB.

Applications can forward WebSocket clients inbound messaging to the secure/inbound topic by setting enableWebSocketInboundTopic to TRUE in application configuration. Afterwards, any authorized WebSocket client can receive and process those messages:

// NOTE: A valid client access_token is still required here
const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)

ws.on('message', (event) => {
  // Parse incoming WebSocket messages
  const { topic, messageType, data } = JSON.parse(event.data)

  if (topic === 'main' && messageType === 'welcome') {
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: 'secure/inbound',
        auth: 'valid subscription JWT here...'
      }
    }))
  } else if (topic === 'secure/inbound' && messageType === 'broadcast') {
    // PROCESS WEBSOCKET MESSAGES HERE...
  }
})

Also see: Subscribing to secure topics below

Applications can forward WebSocket clients inbound messaging to a target Amazon SQS Queue, leveraging SQS scalability, consumer parallelism and reliability.
To enable Amazon SQS Queue forwarding, go to Modify Application / WebSocket Inbound Messaging / Amazon SQS Forwarding Target Configuration and provide the requested configuration.

Optionally see an example backend service which demonstrates how to respond to client requests via SQS integration: SQS Backend example

Example SQS consumer message format:

{
  "client": {
    "connectionId": "a5c07d1d1a310eb55d9d038e71e9c986",
    "subject": "user-01"
  },
  "payload": "Saying hi from WS client... ",
  "id": "3bb24d51855461cb55511b789cc1418e"
}

Applications can forward WebSocket clients inbound messaging to a target Amazon SNS Topic, leveraging SNS's extensive range of supported event targets.
To enable Amazon SNS Topic forwarding, go to Modify Application / WebSocket Inbound Messaging / Amazon SNS Forwarding Target Configuration and provide the requested configuration.


WebSocket Inbound ACK

When enabling WebSocket Inbound ACK in application configuration, the server will acknowledge each successful processed message. The payload will contain the submitted (or generated if absent) message ID and is deliverd to the client via the priv/acks topic.

This functionality not only confirms the successful receipt and forwarding of messages but also enhances tracking and integrity of data flow within your application and clients.

ws.on('message', (event) => {
  // Parse incoming WebSocket messages
  const { topic, messageType, data } = JSON.parse(event.data)

  if (topic === 'main' && messageType === 'welcome') {
    ws.send(JSON.stringify({
    type: 'message',
      data: {
        payload: 'hello',
        id: '528c3s4-3d8y'
      }
    }))
  } else if (topic === 'priv/acks' && messageType === 'ack') {
    // retrieving acknowledged message ID
    const messageId = data

    // PROCESS ACKNOWLEDGED MESSAGES HERE...
  }
})

Management Services

Management services refer to administration and telemetry APIs supporting the platform beyond the servers, APIs which are mainly used for administrative and reporting purposes. Example of management services are:

  • Apps Manager API
  • Telemetry processors
  • Telemetry and Reporting API
  • Settings API

Checkout our OpenAPI documentation: https://api-docs.r7.21no.de

Works with the Chrome browser only!

API

The following APIs are provided by the real-time servers and are designed for use by application subscribers, admins, or operators (for example, publishers).

See Management Services below for other administrative APIs

WebSockets

  • Open session:
    const ws = new WebSocket(`wss://genesis.r7.21no.de/apps/${APP_ID}?access_token=${ACCESS_TOKEN}`)
    ws.onmessage = (event) => {
      // Parse incoming WebSocket messages
      const { topic, messageType, data } = JSON.parse(event.data)
    
      if (topic === 'main' && messageType === 'welcome') {
        // SESSION IS NOW READY, YOU CAN SUBSCRIBE TO APP TOPICS...
      }
    } 
    
  • Subscribe topics:
    // subscribing public topics
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: TOPIC_NAME
      }
    }))
    
    // subscribing secure topics
    ws.send(JSON.stringify({
      type: 'subscribe',
      data: {
        topic: `secure/${TOPIC_NAME}`,
        auth: AUTH_TOKEN // see below: "Subscribing to secure topics"
      }
    }))
    
  • Unsubscribe topics:
    // unsubscribing public topics
    ws.send(JSON.stringify({
      type: 'unsubscribe',
      data: {
        topic: TOPIC_NAME
      }
    }))
    
    // unsubscribing secure topics
    ws.send(JSON.stringify({
      type: 'unsubscribe',
      data: {
        topic: `secure/${TOPIC_NAME}`
      }
    }))
    
  • Process incoming messages:
    // processing incoming messages
    ws.onmessage = (event) => {
      // Parse incoming WebSocket messages
      const { topic, messageType, data } = JSON.parse(event.data)
    
      if (topic === 'main' && messageType === 'welcome') {
        // SESSION IS NOW READY, YOU CAN SUBSCRIBE TO APP TOPICS...
      } else if (messageType === 'broadcast') {
        logger.info(`> processing message from topic '${topic}'...`)
        logger.info(data)
    
        // MESSAGE PROCESSING LOGIC HERE
      }
    } 
    
  • Send messages:
    // subscribing public topics
    ws.send(JSON.stringify({
      type: 'message',
      data: {
        // your message payload, expected type: object or string
        payload: 'hello',
        // message id, auto-generated if not provided
        id: '52',
        // compression, default value: false
        compress: true 
      }
    }))
    
  • Close session:
    ws.close()
    

NOTE: When input message validation fails for the WebSocket API, the client is immediately disconnected with reason: Invalid message format!

WebSocket clients and payload compression

WebSocket clients can vary in their support for message payload compression, often reflected in the different API abstractions available to developers. The following examples illustrate these differences across various libraries:

  • Web Browsers (Google Chrome, Mozilla Firefox and Safari):

      ws.onmessage = async (event) => {
        // Parse incoming WebSocket messages
        const { topic, messageType, data } = event.data instanceof Blob
          ? JSON.parse(await event.data.text()) // compression is enabled
          : JSON.parse(event.data)
    
        // Check if it's a welcome message from the 'main' topic
        if (topic === 'main' && messageType === 'welcome') {
          console.log('> Connected!')
        }
    
        // Log incoming WebSocket messages
        console.log('> Incoming message:', { topic, messageType, data, compression: event.data instanceof Blob })
      }
    

    Full demo: https://github.com/BackendStack21/realtime-forum/blob/main/demos/browser/subscriber.html

  • Node.js with ws and reconnecting-websocket libraries:

      ws.onmessage = (event) => {
        // Parse incoming WebSocket messages
        // Compression is handled transparently, no need for decoding on handler
        const { topic, messageType, data } = JSON.parse(event.data)
    
        // Check if it's a welcome message from the 'main' topic
        if (topic === 'main' && messageType === 'welcome') {
          console.log('> Connected!')
        }
    
        // Log incoming WebSocket messages
        console.log('> Incoming message:', { topic, messageType, data })
      }
    

    Full demo: https://github.com/BackendStack21/realtime-forum/blob/main/demos/node/auto-reconnection.js

  • Node.js with websocket library:

      ws.onmessage = (event) => {
        // Parse incoming WebSocket messages
        const { topic, messageType, data } = event.data instanceof ArrayBuffer
          ? JSON.parse(new TextDecoder().decode(event.data)) // compression is enabled
          : JSON.parse(event.data)
    
        // Check if it's a welcome message from the 'main' topic
        if (topic === 'main' && messageType === 'welcome') {
          console.log('> Connected!')
    
          // Subscribe to a custom topic
          ws.send(JSON.stringify({
            type: 'subscribe',
            data: {
              topic: 'my-custom-topic'
            }
          }))
        }
    
        // Log incoming WebSocket messages
        console.log('> Incoming message:', { topic, messageType, data, compression: event.data instanceof ArrayBuffer })
      }
    

    Full demo: https://github.com/BackendStack21/realtime-forum/blob/main/demos/node/subscriber.js

HTTP

The following considerations are effective when invoking protected endpoints below:

  • Max message payload size is 5KB
  • Each endpoint invocation reduces your API usage quota by a certain amount of bytes
  • Authentication tokens are required to be verifiable using the JWT verification settings defined in application adminKey configuration attribute
    Sample token format with supported roles:
    {
      "roles": [
        "Publisher",
        "SubscribersAdmin",
        "ConnectionsAdmin"
      ],
      "iat": 1673028921,
      "exp": 1673028926,
      "iss": "https://signer.example.com",
      "sub": "service-name",
      ...
    }
    
    Exceptionally, publishers also required to include an allowedTopics attribute in the payload:
    {
      "roles": [
        "Publisher"
      ],
      "allowedTopics": [
        "*"
      ],
      "iat": 1673028921,
      "exp": 1673028926,
      "iss": "https://signer.example.com",
      "sub": "service-name",
      ...
    }
    

    See: Granting publisher permissions on topics

Endpoints

  • Server health status: GET /health/status, unprotected
    curl -X GET \
    -H "Accept:application/json" \
    "https://genesis.r7.21no.de/api/health/status"
    
  • Publish a message on a topic: POST /topics/:appId/publish, required role: Publisher, cost in bytes: data.length
    curl -X POST \
    -H "Accept:application/json" \
    -H "Content-Type:application/json" \
    -H "Authorization:Bearer XXX" \
    --data '{"topic":"main","message":{"hello":"world"},"compress":false}' \
    "https://genesis.r7.21no.de/api/topics/$APPPLICATION_ID/publish"
    
  • Unsubscribe clients from one or many topics: POST /topics/:appId/unsubscribe, required role: SubscribersAdmin, cost in bytes: 256
    curl -X POST \
    -H "Accept:application/json" \
    -H "Content-Type:application/json" \
    -H "Authorization:Bearer XXX" \
    --data '{"topicPatterns":["secure/logs/*"],"subject":"user0@example.com"}' \
    "https://genesis.r7.21no.de/api/topics/$APPPLICATION_ID/unsubscribe"
    
    The request above would unsubscribe clients where subject = user0@example.com from all topics matching the pattern secure/logs/*. Read more about supported matching patterns at https://github.com/sindresorhus/matcher#patterns:
    Use * to match zero or more characters.
    A leading ! negates the pattern.
    
  • Terminate a connection(close socket) by it's connection identifier: DELETE /connections/:appId/:connectionId, required role: ConnectionsAdmin, cost in bytes: 256
    (please note that re-connection logic might cause the to reconnect after a termination)
    curl -X DELETE \
    -H "Accept:application/json" \
    -H "Authorization:Bearer XXX" \
    "https://genesis.r7.21no.de/api/connections/$APPPLICATION_ID/$CONNECTION_ID"
    
  • Terminate all connections(close sockets) to an application: DELETE /connections/:appId, required role: ConnectionsAdmin, cost in bytes: 256
    (please note that re-connection logic might cause clients to reconnect after a termination)
    curl -X DELETE \
    -H "Accept:application/json" \
    -H "Authorization:Bearer XXX" \
    "https://genesis.r7.21no.de/api/connections/$APPPLICATION_ID"
    

Administration Console

Management of applications

`Realtime Pub/Sub` - Managing Applications

YouTube Video: Realtime Pub/Sub - Managing Applications

Accessing application telemetry and reports via REST

Application owners can also access their application telemetry reports via REST in order to enable or extend custom monitoring capabilities.

The following example illustrates how to query the telemetry API endpoints using administrative tokens:

const jwt = require('jsonwebtoken')
const axios = require('axios').default
const ADMIN_ROLE = 'ApplicationAdmin'

function getAppAggregatedTelemetry (cfg, days) {
  console.info(`Getting aggregated telemetry from ${cfg.appId} application...`)

  const AUTH_TOKEN = getAppAdminToken(cfg.adminSigningKey, cfg.tokenExpiresInSeconds)
  const options = {
    method: 'GET',
    url: `https://${cfg.apiGatewayUrl}/telemetry/aggregates/${cfg.appId}/${days}`,
    headers: {
      'Content-Type': 'application/json',
      Authorization: AUTH_TOKEN,
      'X-App-Id': cfg.appId
    }
  }

  return axios.request(options)
}

function getAppAdminToken (secret, expiresIn) {
  return 'Bearer ' + jwt.sign({ roles: [ADMIN_ROLE] }, secret, {
    algorithm: 'ES512', 
    expiresIn,
    issuer: 'https://signer.example.com',
    subject: 'service-name'
  })
}

getAppAggregatedTelemetry({
  appId: 'YOUR APPLICATION ID',
  adminSigningKey: 'APPLICATION ADMIN SECRET/PRIVATE KEY',
  tokenExpiresInSeconds: 5,
  apiGatewayUrl: 'api-gateway.r7.21no.de' // OR PROVIDED CLUSTER API GATEWAY URL
}, 1)
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err))
Depending on the environment, administrators and operators may issue tokens with longer expiration periods. In the previous example, we used short-lived tokens (5 seconds).
Also note that the responsibility of issuing tokens commonly lies with a separate service and not with the telemetry consumer itself, included here for demonstration purposes.

Security Considerations

Embrace public key authentication with JWT

We would like our customers to keep in control of their authentication strategies, including their JWT signing keys. Always use asymmetric keys for production applications!

Applications related authentication takes place in two stages:

  • WebSocket clients authentication: Uses JWT verification details defined in the authKey application configuration attribute to decide if a subscriber can connect to a target application
    ...
    "authKey": {
      "key": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGBy...RzY1gqGo=\n-----END PUBLIC KEY-----",
      "algorithms": [
        "ES512"
      ],
      "issuer": []
    }
    ...
    
  • Invoking administrative HTTP endpoints: Uses JWT verification details defined in the adminKey application configuration attribute to authorize operations supported by the HTTP endpoints in real-time servers (see above)
    ...
    "adminKey": {
      "key": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGBy...RzY1gqGo=\n-----END PUBLIC KEY-----",
      "algorithms": [
        "ES512"
      ],
      "issuer": []
    }
    ...
    

When necessary, application owners can re-use the same configuration for both attributes (authKey and adminKey), it works as intended.

In both cases, application owners are encouraged to use asymmetric encryption and digital signature algorithms. Opposed to HS* algorithms, RSA and ECDSA based algorithms require application owners to submit only their public key, retaining cryptographic control and authority over the issuing of short-living authentication tokens.

🔐 The use of asymmetric algorithms ensure stronger security features to application owners, being PS384, PS512, ES384, ES512 preferred candidates. Optionally, you can generate JWT keys at: https://jwt-keys.21no.de

Subscribing to secure topics

As previously mentioned, "secure" topics are those with a secure/ prefix and are designed to be the channel for restricted messages which are only available to subscribers with required permissions.

Subscribing to a secure topic requires an authorization token (JWT) with a custom payload. Let's review an example below:

{
  "roles": [
    "Subscriber"
  ],
  "allowedTopics": [
    "secure/logs/*"
  ],
  "iat": 1675335604,
  "exp": 1675335609,
  "iss": "https://jwt-signer.r7.21no.de",
  "sub": "user0@example.com"
}

A valid token with the structure above would allow clients to subscribe to secure topics which name matches the secure/logs/* pattern. Please also note that during the token verification process, the calling client authentication sub attribute should be equal to one passed in the token. Entries in the allowedTopics attribute are matched using the matcher module.

Abstract JavaScript example

Somewhere in your Security Token Service...

const jwt = require('jsonwebtoken')
const fs = require('fs')
const RSA_PRIVATE_KEY = "..."
const ALL_SECURE_LOGS_TOPICS = ["secure/logs/*"]

function getAuthToken (subject, allowedTopics) {
  const payload = { 
    roles: ['Subscriber'], 
    allowedTopics 
  }

  return jwt.sign(payload, RSA_PRIVATE_KEY, {
    algorithm: 'RS256',
    expiresIn: 5,
    issuer: 'https://signer.example.com',
    subject
  })
}

// expose endpoint that allows authenticated clients to securely request their subscription tokens
...

Somewhere in your WebSocket client lifecycle...

...
ws.send(JSON.stringify({
  type: 'subscribe',
  data: {
    topic: 'secure/logs/fusion-engine-9',
    auth: '...' // token retrieved from Security Token Service...
  }
}))

End to end encryption

At this time, the platform does not support built-in end-to-end encryption. Nevertheless, developers with specific encryption needs, implementing a custom encryption layer is straightforward and feasible.

It's important to clarify that the platform does not retain messages nor does it extract metadata from the content. Instead, we gather data primarily for reporting and billing purposes. Here's a JSON sample showcasing the kind of data we collect, which includes the message length and the count of subscribers for a given topic:

// portion of the response payload returned by the publication endpoint (Telemetry API)

{
  "bytes": 1175641,
  "subscribers": 17101,
  "records": [
    {
      "_id": "2023-02-01T17:00:00.000Z",
      "ops": 24,
      "bytes": 11476,
      "subscribers": 164
    },
    {
      "_id": "2023-02-01T17:30:00.000Z",
      "ops": 45,
      "bytes": 23317,
      "subscribers": 337
    },
    {
      "_id": "2023-02-01T18:00:00.000Z",
      "ops": 45,
      "bytes": 21975,
      "subscribers": 315
    },
    ...

For more information, please refer to our Privacy Policy to know about the data that we collect.

Other low-level WebSocket server settings

The following are low-level WebSocket server settings that are not configurable by application owners:

  • Max "Back-Pressure" buffer size per connection is 64KB, on buffer overflow, the connection is terminated.
  • Max message payload size is 5KB.
  • Server will send pings automatically to uphold a stable connection.
  • Connection idle timeout is 30 seconds, afterwards the connection is closed.
  • Each user is restricted to only one WebSocket connection for each server instance.

    A minimum of five server instances are consistently operational in our PROD environment.