Migrating the WebSocket Machine Readable News Application to Version 2 Authentication

Wasin Waeosri
Developer Advocate Developer Advocate

Introduction

Important Note:

  • If you are using the Wealth solution (Pricing Streaming Optimized Service or Pricing Streaming Service), the products currently support Authentication Version 1 only (As of May 2024). Please contact your LSEG representative to verify if you are using Wealth or RTO solution.
  • For RTO customers, please be informed that the Authentication Version 1 is currently limited support. Please refer to PCN207737 and PCN208969 documents for more detail.

The LSEG Real-Time - Optimized (RTO) gives you access to best-in-class Real-Time market data delivered in the cloud, including the Machine Readable News (MRN) data. The RTO utilizes the Delivery Platform (RDP - formerly known as Refinitiv Data Platform) Authentication Service to obtain Access Token information. The RDP's Version 2 Authentication or commonly known as Customer Identity and Access Management (CIAM) or Service ID is a newly introduced authentication service for RTO. It provides developers more resilient, reliable, and secure service to the products and solutions.

This article show a step-by-step guide about how to migrate existing Websocket API MRN application to Version 2 Authentication (CIAM). The code is based on Python Introduction to Machine Readable News with WebSocket API article and the WebSocket - RTO market_price_rdpgw_client_cred_auth.py example.

What is the Version 2 Authentication?

Let me start by an overview of Version 2 Authentication (CIAM). The first step of an application workflow is to get a token from RDP Authentication Service, which will allow access to the protected resource, i.e. data REST API, streaming services (RTO), etc.

The RDP's Version 2 Authentication (or simply known as Customer Identity and Access Management - CIAM or Service ID) service uses the oAuth2.0 Client Credentials Grant and Client Credentials with Private JWT Key models for authentication.

The OAuth 2.0 - Client Credentials Grant Model uses the client_id as a Service ID (public identifier for apps) and client_secret as a Password to authenticate clients for this request. The request grant_type parameter must be set to client_credentials value. Once the authentication is successful, the RDP Authentication service generates an access token (access_token*) with additional information (expires_intoken_type) with the response message.

RTO Version 2 Authentication HTTP flow

For more detail about the Version 2 Authentication, please check the following resources:

That’s all I have to say about CIAM Authentication.

What is the Machine Readable News (MRN)?

Now let me turn to an overview of Machine Readable News. Machine Readable News (MRN) is an advanced service for automating the consumption and systematic analysis of news. It delivers real-time news data, deep historical news archives, ultra-low latency structured news and news analytics directly to your applications via the Real-Time Platform using an Open Message Model (OMM) envelope in News Text Analytics domain message.

The Real-time News content set is made available over MRN_STORY RIC. The content data is contained in a FRAGMENT field that has been compressed and potentially fragmented across multiple messages, to reduce bandwidth and message size.

A FRAGMENT field has a different data type based on a connection type:

The data goes through the following series of transformations:

  1. The core content data is a UTF-8 JSON string
  2. This JSON string is compressed using gzip
  3. The compressed JSON is split into several fragments (BUFFER or Base64 ASCII string) which each fit into a single update message
  4. The data fragments are added to an update message as the FRAGMENT field value in a FieldList envelope

Therefore, to parse the core content data, the application will need to reverse this process.

To learn more about the MRN data structure, its behavior and step-by-step guide, please refer to the Introduction to Machine Readable News with WebSocket API article.

That covers MRN data overview.

Prerequisite

This project requires the following dependencies and software.

  1. Python (at least version 3.10.x) or Anaconda/Miniconda Python distribution.
  2. Internet connection.
  3. Access to the Real-Time Optimized (Not Wealth products) with Authentication Version 2 (aka Customer Identity and Access Management - CIAM, or Service Account) and Machine Readable News data permission.

Please contact your LSEG representative to help you to access the RTO Service account and services.

How to migrate the MRN application to the RTO Version 2 Authentication

My next point is how to migrate the current RTO Version 1 Authentication to Version 2. You may noticed that I am not focusing much on the MRN data process. The reason is when migrating the application to the RTO Version 2 Authentication, the data subscription, and data processing logic are the same. Only the RDP HTTP login and the WebSocket connection logic source code that need to be changed to support the Version 2 Authentication workflow.

RTO Version 2 Authentication streaming workflow

Step 1: Change the RDP Authentication Logic

Moving on to RDP HTTP authentication process. The first step of the migration process is to change an application RDP HTTP authentication logic. The summary of changes need to be done are as follows:

  1. Change the API URL endpoint to https://api.refinitiv.com/auth/oauth2/v2/token
  2. Change a HTTP request message payload to send client_id and client_secret with grant_type=client_credentials
  3. An application keeps only access_token and expires_in, no more refresh token logic.

An application source code that applied changes above is shown below. Please be noticed that we check if response's HTTP status is 200 and then get the access_token and expires_in information from the RDP.

    	
            

import time

 

auth_url = 'https://api.refinitiv.com/auth/oauth2/v2/token'

auth_token = ''

expire_time = ''

 

def get_auth_token(url=None):

    """

        Retrieves an authentication token.

    """

 

    if url is None:

        url = auth_url

 

    data = {'grant_type': 'client_credentials', 'scope': scope, 'client_id': clientid, 'client_secret': client_secret}

 

    try:

        # Request with auth for https protocol    

        r = requests.post(url,

                headers={'Accept' : 'application/json'},

                          data=data,

                          verify=True,

                          allow_redirects=False, timeout= 45)

 

    except requests.exceptions.RequestException as e:

        print('Delivery Platform authentication exception failure:', e)

        return None, None

   

    if r.status_code == 200:

        auth_json = r.json()

        print(json.dumps(auth_json, sort_keys=True, indent=2, separators=(',', ':')))

        return auth_json['access_token'], auth_json['expires_in']

 

...

 

if __name__ == "__main__":

    ...

    auth_token, expire_time = get_auth_token()

    tokenTS = time.time()

    ...

   

You may be noticed tokenTS = time.time() statement after we got an access token. This code is for keeping the time that we got an access token and it will be used on the Step 5.

Let’s leave the get_auth_token() method and Version2 Authentication process there.

Step 2: Request a list of RTO WebSocket Servers from RDP Service Discovery

Note: This step is the same as the Version 1 Authentication.

Now, what about the Service Discovery? The RDP Service Discovery provides a list of RTO matches tier endpoints with each url, port, connection type, etc. information for an application.

Once an application get an access token from the RDP Authentication, an application can just send a HTTP GET request message to https://api.refinitiv.com/streaming/pricing/v1/ API endpoint with an access token from the RDP Authentication Service in the request message header.

    	
            

discovery_url = 'https://api.refinitiv.com/streaming/pricing/v1/'

region = 'ap-southeast-1'

 

def query_service_discovery(url=None):

    """

        Retrieves list of endpoints.

    """

    if url is None:

        url = discovery_url

 

    try:

        r = requests.get(url,

                         headers={'Authorization': f'Bearer {auth_token}'},

                         params={'transport': 'websocket'},

                         allow_redirects=False , timeout= 45)

 

    except requests.exceptions.RequestException as e:

        print('Delivery Platform service discovery exception failure:', e)

        return False

 

if __name__ == "__main__":

    ...

    if not query_service_discovery():

        print('Failed to retrieve endpoints from Delivery Platform Service Discovery. Exiting...')

        sys.exit(1)

Next, once an application confirms it gets a response message from RDP with HTTP status 200, get a list of RTO endpoint and choose the endpoint based on an application's requirement. My demo application just choosing the first endpoint in the list from the ap-southeast-1 region that contains multiple AWS availability zone (AZ - Service Discovery location fields).

    	
            

region = 'ap-southeast-1'

 

def query_service_discovery(url=None):

    """

        Retrieves list of endpoints.

    """

    # Requests Code above

 

    for index in range(len(response_json['services'])):

        if not response_json['services'][index]['location'][0].startswith(region):

            continue



        if len(response_json['services'][index]['location']) >= 2:

            hostList.append(response_json['services'][index]['endpoint'] + ":" + str(response_json['services'][index]['port']))

            continue

That covers the Service Discovery source code walkthrough.

Caution

If an application receives a Service Discovery response message with HTTP code: 403 Forbidden status, it means your Version 2 Authentication credential do not have a permission for the RDP /streaming/pricing/v1/ API endpoint. Please contact your LSEG representative or Account Manager to verify your account permission.

Step 3: Connect to the RTO WebSocket Server

Note: This step is the same as the Version 1 Authentication.

The next step is connecting to the selected WebSocket server (from step#2) using the WebSocket library. I am demonstrating with the websocket-client library.

    	
            

if __name__ == "__main__":

    ...

    # Start websocket handshake;

    session1 = WebSocketSession('Session1', hostList[0])

    session1.connect()

...

# Operations

def connect(self):

    ''' Connect to RTO WebSocket '''

    # Start websocket handshake

    ws_address = f'wss://{self.host}/WebSocket'

    #websocket.enableTrace(True)

    if (not self.web_socket_app) or self.reconnecting:

        self.web_socket_app = websocket.WebSocketApp(ws_address,

                                                    on_message=self._on_message,

                                                    on_error=self._on_error,

                                                    on_close=self._on_close,

                                                    on_open=self._on_open,

                                                    subprotocols=['tr_json2'])

    # Event loop

    if not self.wst:

        print(f'{str(datetime.now())} {str(self.session_name)}: Connecting WebSocket to {ws_address} ...')

        self.wst = threading.Thread(target=self.web_socket_app.run_forever, kwargs={'sslopt': {'check_hostname': False}})

        self.wst.daemon = True

        self.wst.start()

    elif self.reconnecting and not self.force_disconnected:

        print(f'{str(datetime.now())} {str(self.session_name)}: Reconnecting WebSocket to {ws_address} ...')

        self.web_socket_app.run_forever()

And then sends a WebSocket Login request message to RTO once a WebSocket connection has been established (you can check it via _on_open callback method).

    	
            

# ---JSON-OMM Process functions ---#

def _send_login_request(self, authn_token):

    """

        Send login request with authentication token.

        Used both for the initial login and subsequent reissues to update the authentication token

    """

    login_json = {

        'ID': 1,

        'Domain': 'Login',

        'Key': {

            'NameType': 'AuthnToken',

            'Elements': {

                'ApplicationId': '',

                'Position': '',

                'AuthenticationToken': ''

            }

        }

    }

 

    login_json['Key']['Elements']['ApplicationId'] = app_id

    login_json['Key']['Elements']['Position'] = position

    login_json['Key']['Elements']['AuthenticationToken'] = authn_token

 

    self.web_socket_app.send(json.dumps(login_json))

    print(str(datetime.now()) + " SENT on " + self.session_name + ":")

    print(json.dumps(login_json, sort_keys=True, indent=2, separators=(',', ':')))

Please be noticed that the JSON Login request message has NameType property with AuthnToken value, and AuthenticationToken property with an access token value from RDP Authentication Service. This logic is the same as Version 1 Authentication.

Step 4: Subscribe to MRN RIC and parse MRN data

Note: This step is the same as the Version 1 Authentication and RTDS connections.

Now we come to the MRN data subscription and processing part. An application can send an item request with MRN RIC (MRN_STORY) to RTO, and receives incoming MRN messages, and then process the MRN messages to get the JSON news data.

This step logic is the same with every type of connection (RTO or deployed RTDS). The version of RDP Authentication also do not impact to the MRN data processing logic as well. The source code that run MRN process is the same.

MRN data processing workflow

Please see more detail on How to process MRN data and Code Walkthrough sections of the Introduction to Machine Readable News with WebSocket API article.

That’s all I have to say about the MRN data request and assemble news logic.

Step 5: Handle WebSocket Connection

So, now let’s look at how to handle the WebSocket connection. This part lets an application controls when to renew an access token from RDP.

One major Version 2 Authentication advantage over V1 is an application does not need to renew an access token (both RDP HTTP and WebSocket streaming) as long as a WebSocket connection is active. The login session to the RTO Streaming Server will remain valid (even if the connection time passes an access Token expiry period) until the consumer disconnects or is disconnected from the RTO Streaming server. An application only need re-request an Access Token in the following scenarios:

  • When an application disconnects and goes into a reconnection state.
  • Then it stays in reconnection state long enough to get close to the expiry time of an Access Token.

That means, an application needs to change a connection and renew access token logic to

  • no need to renew an access token periodically
  • check connection state (connectedre-connect, and disconnect, etc)

The modified code to apply the logic above are as follows:

    	
            

while True:

    # NOTE about connection recovery: When connecting or reconnecting

    #   to the server, a valid token must be used. Upon being disconnecting, initial

    #   reconnect attempt must be done with  a new token.

    #   If a successful reconnect takes longer than token expiration time,

    #   a new token must be obtained proactively.

 

    # Waiting a few seconds before checking for connection down and attempting reconnect

    time.sleep(5)

    if not session1.web_socket_open:

        if session1.reconnecting:

            curTS = time.time()

            if (int(expire_time) < 600):

                DELTA_TIME = float(expire_time) * 0.05

            else:

                DELTA_TIME = 300

            if (int(curTS) >= int(float(tokenTS) + float(expire_time) - float(DELTA_TIME))):

                auth_token, expire_time = get_auth_token()

                tokenTS = time.time()

        else:

            auth_token, expire_time = get_auth_token()

            tokenTS = time.time()

 

        if not session1.web_socket_open and not session1.force_disconnected:

            session1.reconnecting = True

 

        if auth_token is not None:

            if (not session1.force_disconnected) and session1.reconnecting:

                session1.connect()

        else:

            print('Failed authentication with Delivery Platform. Exiting...')

            sys.exit(1)

Please find a step-by-step explanation of each code part from Access Token Renewal section of my colleague's Real-Time WebSocket API: The Real-Time Optimized Version 2 Authentication Migration Guide article. The main concept is to renew access token when an application is in reconnection state and the time gets close to the expiry time of an Access Token only.

You see that an application does not need to renew an access token every 8-9 minutes anymore.

Note: An application is still need to handle the WebSocket Ping-Pong message manually like other types of connection.

Step 5.5: Renew an Access Token

My next point is renewing an Access Token code walkthrough. With the Version 2 Authentication, an application just re-send a HTTP request message with client_idclient_secret, and grant_type=client_credentials payload to the RDP Authentication Service endpoint (V2) the same way as an initial authentication request.

If you go back to check a get_auth_token() method, you will be noticed that the code is much simplifier than the Version 1 Authentication source code since an application does not need to do a refresh_token logic anymore.

That is all for the code updated to support the Version 2 Authentication.

Running an application.

Turning to how to run an example application. You need to download the example project into a directory of your choice, and then follow the steps on Set Up A Console Environment and RTO Version 2 Authentication Console Example sections of the GitHub project

Example Results

Send MRN_STORY request to RTO

    	
            

SENT:

{

  "Domain":"NewsTextAnalytics",

  "ID":2,

  "Key":{

    "Name":"MRN_STORY",

    "Service":"ELEKTRON_DD"

  }

}

2024-07-02 17:13:41.513258 RECEIVED on Session1:

[

  {

    "Domain":"NewsTextAnalytics",

    "Fields":{

      "ACTIV_DATE":"2024-07-01",

      "CONTEXT_ID":3752,

      "DDS_DSO_ID":4232,

      "FRAGMENT":null,

      "FRAG_NUM":1,

      "GUID":null,

      "MDUTM_NS":null,

      "MDU_DATE":null,

      "MDU_V_MIN":null,

      "MRN_SRC":"HK1_PRD_A",

      "MRN_TYPE":"STORY",

      "MRN_V_MAJ":"2",

      "MRN_V_MIN":"10",

      "PROD_PERM":10001,

      "RDN_EXCHD2":"MRN",

      "RECORDTYPE":30,

      "SPS_SP_RIC":".[SPSML2L1",

      "TIMACT_MS":56440183,

      "TOT_SIZE":0

    },

    "ID":2,

    "Key":{

      "Name":"MRN_STORY",

      "Service":"ELEKTRON_DD"

    },

    "PermData":"AwEBEAAc",

    "Qos":{

      "Rate":"JitConflated",

      "Timeliness":"Realtime"

    },

    "SeqNumber":57886,

    "State":{

      "Data":"Ok",

      "Stream":"Open"

    },

    "Type":"Refresh"

  }

]

RECEIVED: Refresh Message

Name = PROD_PERM: Value = 10001

Name = ACTIV_DATE: Value = 2024-07-01

Name = RECORDTYPE: Value = 30

Name = RDN_EXCHD2: Value = MRN

Name = TIMACT_MS: Value = 56440183

Name = GUID: Value = None

Name = CONTEXT_ID: Value = 3752

Name = DDS_DSO_ID: Value = 4232

Name = SPS_SP_RIC: Value = .[SPSML2L1

Name = MRN_V_MAJ: Value = 2

Name = MRN_TYPE: Value = STORY

Name = MDU_V_MIN: Value = None

Name = MDU_DATE: Value = None

Name = MRN_V_MIN: Value = 10

Name = MRN_SRC: Value = HK1_PRD_A

Name = MDUTM_NS: Value = None

Name = FRAG_NUM: Value = 1

Name = TOT_SIZE: Value = 0

Name = FRAGMENT: Value = None

Receive Update messages and assemble News

    	
            

2024-07-02 17:13:43.927864 RECEIVED on Session1:

[

  {

    "DoNotCache":true,

    "DoNotConflate":true,

    "DoNotRipple":true,

    "Domain":"NewsTextAnalytics",

    "Fields":{

      "ACTIV_DATE":"2024-07-02",

      "FRAGMENT":"H4sIAAAAAAAC/3VUbW/bNhD+vl9B6Msa1PFkxUUK5ctc+aWOLVmV5DjZPAyUxNhcJNIlKbvCsP++E0klaIcZBp9Hd8e74x2Pfzu4UsvS8R2WfoxuPj+6o8gZOLgpKWEFkY7/uxPF/gJkAGmUGhIYmBqYGQgNbAxkBrZWuFwbEk16xWppyCKxO8LM2qSbeR/P2sSpjZhEsQ3dk3hhwyeT1Lp+mn5y/hg4OS9bOBgyv3CTBpvdAN03VYs89C4hjSJCXqFrlB0JWglSV5QhiWmJOENZQ2SJ2z1TR6wQZqg5Fbym7IDOVFKF8hYtWUlBEQtaExRSRiU4RBEWhJUCo5CXFCm+Z0kjJcXogiXaO2ciWkTrExcKM7V3hnvWZfca/sRfiKzB7bSmCkxjIl/42aSlPWJWIuOR7VksiKQlYQo9VLikNRUobhT4ufCmKlFJZQGmSJAD5QxXevOh4jmu9kySohFUtQOU0wpD6qBXlEhtpAQuyRCZ5P5TH8AQi+KIdHF0WkdcdlXiJ8JAfaYKKwgJ5eO2YCZnfdw9yLsGdEXoKgq1tN24QztIycomDEuFu9JlBJTkXJEzvtszUr6aLJoWzXFTFZzlgpYHctV5fldzWfDLkJGLFJzXv6ojryVnwgQZQiPvrq7grjxTIVUgCJy+GwHP9cbX7u2162Uj14e/6w1d1/0NLI8El3B8AlZvlWjlD5fhZ9l33R4XUTD5ruPgi3axXqftT2/s3rqel28OyXObb0+rYnR5evK2D0X6dXqfzHe7x2P1ns/H7+d1t5tBUbrR3DzDcMI1rzA7NPjQpUYYGNRESvjM2hOIPPiG62k+HEW+qV9OFaad3UnwM9wdAfIo9ZMs6Wb71OQptK6BwXcgjvIbifOKgEY2+V+kUPpFmPhjkCz8kV5vzPpRw4dPGiZmvddgRMFnDVNjPtPrSq9d4NAfrQ18mWr0gtBiZDG2+MViYjG1mFncWnyw+GjxyeB0pDHQ6+zWgIm9XmkwATv3kedP0uWkZ4/xJDA8mM22li3TXrYJLUl63S42ZLq0ZBbOJj1LFpZtk41VL2aRIUuL614QveURzXbpyNLeR7xZG9KHTt/s01mwtYfJkqkhO53+1h9/0BB076XCLyQlX5vu4Xf80cBpxAE4vKE3g+7dkjDQ/zssN/7NrRmWf376F41OE+xVBgAA",

      "FRAG_NUM":1,

      "GUID":"S8N3HX01N_2407022bOgRfybUpKc1wYY2UVcSqDJRFWWXhl+oF4+Fm",

      "MRN_SRC":"HK1_PRD_A",

      "MRN_TYPE":"STORY",

      "MRN_V_MAJ":"2",

      "MRN_V_MIN":"10",

      "TIMACT_MS":36818167,

      "TOT_SIZE":888

    },

    "ID":2,

    "Key":{

      "Name":"MRN_STORY",

      "Service":"ELEKTRON_DD"

    },

    "PermData":"AwEBEAcrEAc7EAdLEBIbECVbECYLECY7ECZ7ECdrECgLECg7ECiLECk7EClLECmbEDALEDAbEDA7EDN7EgALEgAbEgArEgA7EgBLEgFrEgJrEgNbEgOLEgWbEgdrEgeLEhmbEihrEiibEikbEikrEilLEilbEilrEjILEjIbEjIrEjI7EjKbEjQLEjRrEjUrEjY7EkUbEkUrEkU7EkVc",

    "SeqNumber":57918,

    "Type":"Update",

    "UpdateType":"Unspecified"

  }

]

FRAGMENT length = 888

decompress News FRAGMENT(s) for GUID S8N3HX01N_2407022bOgRfybUpKc1wYY2UVcSqDJRFWWXhl+oF4+Fm

News = {'altId': 'nS8N3HX01N', 'audiences': ['NP:G', 'NP:SNS', 'NP:C', 'NP:D', 'NP:E', 'NP:M', 'NP:O', 'NP:T', 'NP:U', 'NP:OIL', 'NP:NAT', 'NP:UKI', 'NP:GRO', 'NP:MTL', 'NP:SOF', 'NP:SNI', 'NP:PSC', 'NP:RNP', 'NP:DNP', 'NP:PGE', 'NP:RAST', 'NP:YDB'], 'body': '       MOSCOW, July 2 (Reuters) - The Kremlin said on Tuesday\nthat an upcoming visit by Indian Prime Minister Narendra Modi to\nRussia was "very important".\n    Kremlin spokesman Dmitry Peskov said Modi and Russian\nPresident Vladimir Putin would discuss regional and global\nsecurity, bilateral ties and trade. \n    The Kremlin said in March that Modi had an open invitation\nto visit Russia.\n  \n\n (Reporting by Reuters; Writing by Anastasia Teterevleva;\nediting by Guy Faulconbridge)\n ((moscow.newsroom@thomsonreuters.com;))', 'firstCreated': '2024-07-02T10:10:02.000Z', 'headline': "Kremlin says visit by India's Modi to Russia is very important", 'id': 'S8N3HX01N_2407022bOgRfybUpKc1wYY2UVcSqDJRFWWXhl+oF4+Fm', 'instancesOf': [], 'language': 'en', 'messageType': 2, 'mimeType': 'text/plain', 'provider': 'NS:RTRS', 'pubStatus': 'stat:usable', 'subjects': ['A:4', 'G:1', 'G:3', 'G:38', 'G:5B', 'G:A', 'G:AJ', 'G:B', 'G:CH', 'G:D3', 'G:E', 'G:K', 'G:S', 'M:1L', 'M:1QD', 'M:2CM', 'M:2CN', 'M:2CP', 'M:2CQ', 'M:2CR', 'M:2CS', 'M:2CT', 'M:2CU', 'M:2CV', 'M:2CX', 'M:2CY', 'M:2D1', 'M:C', 'M:E7', 'M:EL', 'M:LK', 'M:N', 'M:R', 'N2:ASIA', 'N2:ASXPAC', 'N2:CEEU', 'N2:CISC', 'N2:COM', 'N2:CRU', 'N2:CWP', 'N2:DIP', 'N2:EMEA', 'N2:EMRG', 'N2:EUROP', 'N2:GEN', 'N2:IN', 'N2:LEN', 'N2:NASIA', 'N2:NEWS1', 'N2:NRG', 'N2:POL', 'N2:RU', 'N2:SASIA', 'N2:SECUR', 'N2:TRD', 'N2:WEU', 'U:45', 'U:C'], 'takeSequence': 1, 'urgency': 3, 'versionCreated': '2024-07-02T10:13:37.000Z'}

...

Note: The news message is in UTF-8 JSON string format. Some news messages that contain special Unicode characters may not be able to show in Windows OS console (cmd, git bash, powershell, etc) due to the OS limitation. Those messages will be print as UnicodeEncodeError exception. Cannot decode Unicode character message in a console instead.

That covers the MRN data example from RTO.

Next Step and Conclusion

Before I finish, let me just say the Version 2 Authentication brings a lot of improvements to the Real-Time - Optimized (RTO) applications. The V2 simplifies the overall authentication and renew access token process. The V2 workflow source code is simpler to operate when compared to the Version 1 Authentication. Developers can update their application source code to support the V2 without touching the business logic such as data subscription and data processing source code. The data/content behavior is not changed, the QA team can also focus on the V2 authentication and connection test cases only.

If you need more detail about the Version 2 Authentication and the WebSocket API, please check the following resources:

That covers all I wanted to say today.

Reference

For further details, please check out the following resources:

For any question related to this example or WebSocket API, please use the Developer Community Q&A Forum.