# Basic WebSocket Server Message Format

WebSocket is a bidirectional transport: both parties can send any set of data in the JSON format at any time. The protocol implements a "request-response" mechanism, where each incoming message must be acknowledged by the recipient.

# Message Types

The protocol defines three types of messages corresponding to the MESSAGE_TYPE enumeration:

  • RESERVED = 0 – reserved (not in use);
  • REQUEST = 1 – request;
  • RESPONSE = 2 – response.

Currently, only REQUEST and RESPONSE are used.

Any message, whether from the client or the server, contains at least two mandatory fields:

{
  "type": 1,
  "id": 1,
}
Field Type Req. Description
type uint32 Yes Message type: 1 for request (REQUEST), 2 for response (RESPONSE).
id uint32 Yes A unique message identifier, represented as an incrementing number. Used to link request-response pairs. For more details on how to use it, see below.

# Server response

Within the operation of WebSocket, the server can initiate the sending of system messages without a prior request from the client. These messages notify about changes, system events, or actions of other users.

To maintain a stable connection, the client must acknowledge the receipt of each such message. The acknowledgment is sent as a separate response indicating the original event identifier.

{
  "type": 2,
  "id": 123456
}

If the server does not receive a response within 300 seconds, the connection is considered inactive and will be automatically closed, regardless of other signs of activity (e.g., ping/pong).

# Working with the id parameter

Since WebSocket does not have a request-response concept and the order of incoming messages can be arbitrary, the id parameter is used to link outgoing commands with their responses. This parameter must be included in every sent request, and the same value will appear in the incoming message that serves as the response to the request. It can also be interpreted as a counter of sent requests for each party. A response with the same id must be sent for each incoming request.

For example, the client sends a ping message to the server:

{
"type": 1,
"id": 1,
"method": "ping",
"payload": {
  "data": "Hello, World!"
  }
}

The server responds to the client (note the id parameter):

{
  "type": 2,
  "id": 1,
  "payload": {
    "some_field": true
  }
}

Another example. The client sends a message to the server:

{
  "type": 1,
  "id": 23768,
  "method": "getCurrentTime"
}

The server responds:

{
  "type": 2,
  "id": 23768,
  "payload": {
    "data": 1724936321584
  }
}

# Timeout and long operations

If a response to the request is not received within 300 seconds (5 minutes), the server will assume that the connection has been lost or the client has become unresponsive, and will close the WebSocket session. For certain tasks, generating a response may take more than 300 seconds. In such cases, multiple request-response cycles will be generated.

As an example, let's consider the call handling logic. A person may not answer (reject) a call for more than 300 seconds. In such a case, we receive the call status as a separate message from the server after any length of time:

Client request:

{
  "type": 1,
  "id": 4444,
  "method": "callToUser",
  "payload": {
    "callId": "john"
  }
}

The server immediately responds with a call creation confirmation:

{
  "type": 2,
  "id": 1,
  "payload": {
    "userId": "john@as1.trueconfServer.loc#vcs"
  }
}

After some time, when the user answers the call, the server sends a notification to the client:

{
  "type": 1,
  "id": 4445,
  "method": "conferenceCreated",
  "payload": {
    "userId": "john@as1.trueconfServer.loc#vcs",
    "isP2P": true,
    "conferenceId": "477dh6731ac54e322@as1.trueconfServer.loc#vcs"
  }
}

The client confirms that it has received the conference start event:

{
  "type": 2,
  "id": 4445
}

# Duplicate IDs in requests

If we send two requests with the same id, the second request will be rejected, and an error will be returned in response. Similarly, if the id value in a new request is less than an already used value, that request will also be rejected with an error.

Request:

{
  "type": 1,
  "id": 12,
  "method": "ping"
}

Answer:

{
  "type": 2,
  "id": 12
}

Resending request with id = 12:

{
  "type": 1,
  "id": 12,
  "method": "someTest"
}

Error response with errorCode = 2:

{
  "type": 2,
  "id": 12,
  "payload": {
    "errorCode": 2
  }
}

# Asynchronous execution of requests

Since WebSocket libraries provide asynchronous APIs, the client or server can send responses to requests in any order and multiple at a time, without waiting for a response to a previous request. The key point is that the request must be sent within 5 minutes:

Request 1:

{
  "type": 1,
  "id": 278,
  "method": "someLongTask"
}

Request 2:

{
  "type": 1,
  "id": 279,
  "method": "createChat",
  "payload": {
    "chatTitle": "TEST"
  }
}

Request 3:

{
  "type": 1,
  "id": 280,
  "method": "createChat",
  "payload": {
    "chatTitle": "TEST 2"
  }
}

Request 4:

{
  "type": 1,
  "id": 281,
  "method": "someFastTask"
}

Response 1 to request 4:

{
  "type": 2,
  "id": 281
}

Response for request 2:

{
  "type": 2,
  "id": 279,
  "payload": {
    "chatId": "1390r4f03f"
  }
}

Response 3 to request 3:

{
  "type": 2,
  "id": 280,
  "payload": {
    "chatId": "1f4f9f9f39j9f3"
  }
}

Response 4 to request 1:

{
  "type": 2,
  "id": 278
}

# Standard error response

For most requests, in case of an error, a response will be sent in the following format:

{
  "type": 2,
  "id": 1,
  "payload": {
    "errorCode": 200
  }
}
Category Code Name Description
Network Errors100CONNECTION_ERRORConnection error
101CONNECTION_TIMEOUTConnection timeout
102TLS_ERRORTLS/SSL error
103UNSUPPORTED_PROTOCOLUnsupported protocol
104ROUTE_NOT_FOUNDRoute not found
Authorization Errors200NOT_AUTHORIZEDNot authorized
201INVALID_CREDENTIALSInvalid credentials
202USER_DISABLEDUser disabled
203CREDENTIALS_EXPIREDCredentials expired
Chat Errors300INTERNAL_ERRORInternal server error
301TIMEOUTOperation timeout
302ACCESS_DENIEDAccess denied
303NOT_ENOUGH_RIGHTSNot enough rights
304CHAT_NOT_FOUNDChat not found
305USER_IS_NOT_CHAT_PARTICIPANTUser is not a chat participant
306MESSAGE_NOT_FOUNDMessage not found
307UNKNOWN_MESSAGEUnknown message
308FILE_NOT_FOUNDFile not found
309USER_ALREADY_IN_CHATUser already in chat

# Message Structure

Each message received as an event (server request), retrieved by message ID, or found in the chat message history corresponds to an Envelope object:

{
    "method": "sendMessage",
    "type": 1,
    "id": 3,
    "payload": {
        "chatId": "1c1230635432aa7be051e4fda53a3d5a07c8c151",
        "messageId": "dfb127d0-d174-4e11-8394-19482a98607d",
        "timestamp": 1741881175593,
        "author": {
            "id": "user@video.example.com",
            "type": 1
        },
        "isEdited": false,
        "box": {
            "id": 2,
            "position": "0"
        },
        "type": 200,
        "content": {
            "text": "What's up?",
            "parseMode": "text"
        }
    }
}

You can find the description of the parameters in this section.

# Message Storage "Box"

According to TrueConf's internal protocol, each message is placed in a separate storage - a Box, also known as an "envelope" (object EnvelopeBox):

{
"id" : 1,
"position" : ""
}

In most cases, there will be only one message in the box, and to properly sort the messages, it is sufficient to consider the id parameter. The id field is an automatically incrementing number, starting from zero within each chat. In the vast majority of cases, the first message in a chat will have a Box with id = 0, the second with id = 1, and so on. In this case, the position field for each message will be an empty string.

However, in some cases, a single "box" may receive two or more messages. This is a very rare occurrence, possible, for example, when two or more users simultaneously send messages to the chat, and both of those users experience network connection issues.

If two or more messages end up in the same "box," the position field is filled with the ordinal number of that message in the "box." The distinctive feature is that the position of the message in the box is not a number but a string containing the characters 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz{}.

To sort messages by position, it is sufficient to compare two strings based on their ASCII codes. This type of comparison is available by default in most programming languages.

Comparison of three messages that ended up in the same "box":

//Message 1:
{
"id" : 15,
"position" : "A"
}
//Message 2:
{
"id" : 15,
"position" : "B"
}
//Message 3:
{
"id" : 15,
"position" : "AAA"
}

In "standard" string comparison, the ASCII value of each character in the strings is compared sequentially. The string with the greater ASCII value is considered greater. If the ASCII values of all characters are equal, then the string with the greater number of characters is considered greater.

Compare the first and second message:

ASCII("A") = 41  
ASCII("B") = 42  
42 > 41

Thus, the second message has a sequence number greater than the first.

Compare the second and third messages:

ASCII("B") = 66
ASCII("AAA") = 65 65 65
66 < 65

As can be seen, the check does not proceed beyond the first character since the character B has a higher ordinal number than the character A. Therefore, the third message has a lower ordinal number than the second.

Compare the first and third messages:

ASCII("A") = 41
ASCII("AAA") = 41 41 41
41 = 41

The ordinal numbers of characters in both strings match, and in this case, the string with the greater number of characters is considered larger. Thus, the third message has a higher ordinal number than the first.

After sorting, the order of messages will be as follows:

//Message 1:
{
  "id": 15,
  "position": "A"
}
//Message 3:
{
"id" : 15,
"position" : "AAA"
}
//Message 2:
{
"id" : 15,
"position" : "B"
}