Stream and decode reports via WebSocket

In this tutorial, you'll learn how to use Chainlink Data Streams with the Streams Direct implementation and a WebSocket connection. You'll set up your Go project, listen for real-time reports from the Data Streams Aggregation Network, decode the report data, and log their attributes to your terminal.

Requirements

  • Git: Make sure you have Git installed. You can check your current version by running git --version in your terminal and download the latest version from the official Git website if necessary.
  • Go Version: Make sure you have Go version 1.21 or higher. You can check your current version by running go version in your terminal and download the latest version from the official Go website if necessary.
  • API Credentials: Access to the Streams Direct implementation requires API credentials. If you haven't already, contact us to talk to an expert about integrating Chainlink Data Streams with your applications.

Tutorial

Set up your Go project

  1. Clone the repository that contains the project setup for this guide and change directories to the websocket example:

    git clone https://github.com/smartcontractkit/smart-contract-examples.git
    cd smart-contract-examples/data-streams/streams-direct/websocket
    
  2. Execute the command below to download dependencies and generate the go.sum file:

    go mod tidy
    

    Your project directory should now have the following structure:

     websocket/
     ├── client/
     │   └── clientWs.go
     ├── internal/
     │   └── decoder.go
     ├── decodeFullReportHex.go
     ├── go.mod
     ├── go.sum
     └── main.go
    

Set environment variables

Set the required environment variables in your terminal session to authenticate with the Data Streams Aggregation Network:

export BASE_URL="ws.testnet-dataengine.chain.link"
export CLIENT_ID="YOUR_CLIENT_ID"
export CLIENT_SECRET="YOUR_CLIENT_SECRET"
  • BASE_URL is the WebSocket endpoint to subscribe to price updates. See the Streams Direct Interface guide for more information.
  • Replace CLIENT_ID and CLIENT_SECRET with your API credentials.

Establish a WebSocket connection and listen for real-time reports

For this example, you will read from the ETH/USD Data Streams feed on Arbitrum Sepolia. This feed ID is 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782. See the Data Streams Feed IDs page for a complete list of available assets.

Launch the WebSocket listener by running the following command in your terminal:

go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782

Expect output similar to the following in your terminal:

Generating HMAC with the following:  GET /api/v1/ws?feedIDs=0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 <YOUR_CLIENT_ID> 1712557137856
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa08000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c540000000000000000000000000000000000000000000000000000000066138c5400000000000000000000000000000000000000000000000000001a8d2d99af3c0000000000000000000000000000000000000000000000000014193c2106c39c000000000000000000000000000000000000000000000000000000006614ddd40000000000000000000000000000000000000000000000b9b0fee093d4178d800000000000000000000000000000000000000000000000b9b0c6cc20c60f80400000000000000000000000000000000000000000000000b9b136f506e21f9ac00000000000000000000000000000000000000000000000000000000000000002cbfff26b44d58842991f29cd912099b90672f032fbca13b4d35b870c176b50b60396640cc31466f16bcd09a1afe01597fe194870af103d7d4f5c77648358559e000000000000000000000000000000000000000000000000000000000000000232408446b215b7e0ee343144575cb39c63f694ba162ac127336996e1f8b428547a2ce48e6acee6bcb3cab020f92c9bdf3d2fd5fd8e3b832d5c2267bbe5a3a633}}
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c550000000000000000000000000000000000000000000000000000000066138c5500000000000000000000000000000000000000000000000000001a8d2d80abb80000000000000000000000000000000000000000000000000014193b117c4380000000000000000000000000000000000000000000000000000000006614ddd50000000000000000000000000000000000000000000000b9b0ff8f82d63faa400000000000000000000000000000000000000000000000b9b0c9ce73c00f5a000000000000000000000000000000000000000000000000b9b1355091ec6ced4000000000000000000000000000000000000000000000000000000000000000028b26f5ce5210ba244241da391b852d53bb97d76b561c752f12d147c0d0e07565294f878ac10b4f8f94940374339965d3cb1f7194b0d7303a081c26bdd3f4706e000000000000000000000000000000000000000000000000000000000000000278f418c832ad07cab0522e623875c0f5b4c4359cb0ad76c571dbaa02de699ec172b5e9bd4bfc7e2d6667301555434bd01db348112666c264891c59874272c520}}
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c560000000000000000000000000000000000000000000000000000000066138c5600000000000000000000000000000000000000000000000000001a8cefac07b000000000000000000000000000000000000000000000000000141937477f35e0000000000000000000000000000000000000000000000000000000006614ddd60000000000000000000000000000000000000000000000b9b2afff39fd7358400000000000000000000000000000000000000000000000b9b293198c5103b0000000000000000000000000000000000000000000000000b9b2cce4e7a9e300800000000000000000000000000000000000000000000000000000000000000002753b3027be86222709eff93f98059486c278e442535b0c53127908dab09826f22e4fd9d1148550720de61813bd294e3084d724f3d83d972d5abe684a6d9d9ce900000000000000000000000000000000000000000000000000000000000000024236585d1840548b88d2dab324ecb414fbb898f6a9dc4a80979b8f9460802955389e98a95dd26ffc9813489dd2747b6394f95934034ebe1e47fa822bb7419b13}}
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa14000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c570000000000000000000000000000000000000000000000000000000066138c5700000000000000000000000000000000000000000000000000001a8cef5e26300000000000000000000000000000000000000000000000000014191020cfc724000000000000000000000000000000000000000000000000000000006614ddd70000000000000000000000000000000000000000000000b9b2b21fed369590400000000000000000000000000000000000000000000000b9b28e01befceded800000000000000000000000000000000000000000000000b9b2d381feb661a9a00000000000000000000000000000000000000000000000000000000000000002c99d39f03b2e1dc88597401992d0ab2a2ebb829725a7aff4a5b2312dd9d79ccf0447a62620c088d68d577c63029cad7bd46e354775e9d5e1ddb30db3854bb9380000000000000000000000000000000000000000000000000000000000000002158b93031523cda28c976edd47c4b9b6500c13e65026422be3e623de4251fca45ed0107225c022c1a3480cdebc0cb86d5365bc8538702d13d7d80934c12a3275}}
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49aa18000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c580000000000000000000000000000000000000000000000000000000066138c5800000000000000000000000000000000000000000000000000001a8cf081ff1400000000000000000000000000000000000000000000000000141946a6b6b830000000000000000000000000000000000000000000000000000000006614ddd80000000000000000000000000000000000000000000000b9b2aa26b724837a200000000000000000000000000000000000000000000000b9ae56d1e39f64f0000000000000000000000000000000000000000000000000b9b39e26a94459d6a00000000000000000000000000000000000000000000000000000000000000002c633d7ac6c69851ef98d890df1b4ba2d31d9a44fcb4e7d40ed91a6a088cf66c58f194573f0ff92f72d87c49fbb07637a35591a7d5c0746666415a46d6a5300f800000000000000000000000000000000000000000000000000000000000000027be80f9819c263925579e18bae6660cd509f4723ffa2417efe9bcc659045af021e8dad3f288a6e166e1bc43fe8c0e0910becdc395f1ff09602ebec10da2dd120}}
2024/04/08 08:19:04 Websocket: Sending ping...
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab03000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c590000000000000000000000000000000000000000000000000000000066138c5900000000000000000000000000000000000000000000000000001a8cf0bc3f300000000000000000000000000000000000000000000000000014194ce58b2658000000000000000000000000000000000000000000000000000000006614ddd90000000000000000000000000000000000000000000000b9b2a88f4e6b50f2e00000000000000000000000000000000000000000000000b9aeb43cd0a5cd7ec00000000000000000000000000000000000000000000000b9b4d02e31df9123400000000000000000000000000000000000000000000000000000000000000002de39c78c325f14159a4dc9797ccafb0d18a5ec278cd13f5ebc43c3988ce27d304d5174161b14636fcfac22bc06ea83fefd630e6ff826dc5e8ee5fed38adb42f800000000000000000000000000000000000000000000000000000000000000021537709709f273854856f78e0c36acf6580eb87cb23798ff0f8d5fd14b14beed7439265bdd11aca9d9a7f981251fc995c687750cb324d0a61b1e70155d0075b1}}
2024/04/08 08:19:05 Websocket: Received pong...
Received the following message:  {{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c5a0000000000000000000000000000000000000000000000000000000066138c5a00000000000000000000000000000000000000000000000000001a8cf593f46c000000000000000000000000000000000000000000000000001419360d1b077c000000000000000000000000000000000000000000000000000000006614ddda0000000000000000000000000000000000000000000000b9b286b0a60b5f3ca00000000000000000000000000000000000000000000000b9ae8a4a15117180000000000000000000000000000000000000000000000000b9b4eabe86f969652000000000000000000000000000000000000000000000000000000000000000023ede508913fc4066c754edba91d60e12c71911d616231da453e27eba07f34538dc3f32ae21d6ab44cff5fbba64b240f38252ee0a483c6450518257dc748926e900000000000000000000000000000000000000000000000000000000000000021650e1dc132ec7d7ef19672d3ae8c4e0bd1a069439d017664bb665d29a3b415163c6aa6172b2fb6e1658dee2e09292b741fc81cd17cce224e6e790fe3749df2f}}

When you examine a message from the WebSocket connection's output, you notice that it contains two values:

{{0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c5a0000000000000000000000000000000000000000000000000000000066138c5a00000000000000000000000000000000000000000000000000001a8cf593f46c000000000000000000000000000000000000000000000000001419360d1b077c000000000000000000000000000000000000000000000000000000006614ddda0000000000000000000000000000000000000000000000b9b286b0a60b5f3ca00000000000000000000000000000000000000000000000b9ae8a4a15117180000000000000000000000000000000000000000000000000b9b4eabe86f969652000000000000000000000000000000000000000000000000000000000000000023ede508913fc4066c754edba91d60e12c71911d616231da453e27eba07f34538dc3f32ae21d6ab44cff5fbba64b240f38252ee0a483c6450518257dc748926e900000000000000000000000000000000000000000000000000000000000000021650e1dc132ec7d7ef19672d3ae8c4e0bd1a069439d017664bb665d29a3b415163c6aa6172b2fb6e1658dee2e09292b741fc81cd17cce224e6e790fe3749df2f}}
  • The first value 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 is the feedId. In this example, it is the ETH/USD feed on Arbitrum Sepolia.
  • The second value is the FullReport payload in hexadecimal. It contains the encoded report data, which you'll decode in the next section. Save the FullReport payload value for the next step.

Note: In this example, you listen to a single feed. You can listen to multiple feeds by providing the feed IDs as command line arguments separated by spaces. For example:

go run main.go 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265

Decode the reports

  1. Open decodeFullReportHex.go file located at the root of your project directory.

  2. Replace the fullReportHex variable value within the main function with the hexadecimal value you saved earlier from your WebSocket message output, without the 0x prefix:

    [...]
    
    func main() {
    // Sample FullReport payload extracted from the WebSocket message as a hex string
    fullReportHex := "0006f9b553e393ced311551efd30d1decedb63d76ad41737462e2cdbbdff1578000000000000000000000000000000000000000000000000000000001d49ab07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000066138c5a0000000000000000000000000000000000000000000000000000000066138c5a00000000000000000000000000000000000000000000000000001a8cf593f46c000000000000000000000000000000000000000000000000001419360d1b077c000000000000000000000000000000000000000000000000000000006614ddda0000000000000000000000000000000000000000000000b9b286b0a60b5f3ca00000000000000000000000000000000000000000000000b9ae8a4a15117180000000000000000000000000000000000000000000000000b9b4eabe86f969652000000000000000000000000000000000000000000000000000000000000000023ede508913fc4066c754edba91d60e12c71911d616231da453e27eba07f34538dc3f32ae21d6ab44cff5fbba64b240f38252ee0a483c6450518257dc748926e900000000000000000000000000000000000000000000000000000000000000021650e1dc132ec7d7ef19672d3ae8c4e0bd1a069439d017664bb665d29a3b415163c6aa6172b2fb6e1658dee2e09292b741fc81cd17cce224e6e790fe3749df2f"
    
    [...]
    
  3. Run the decoding script by executing the following command in your terminal:

    go run decodeFullReportHex.go
    

    The decodedReport contains structured data about the report, including FeedId and FeedVersion.

    Expect output similar to the following in your terminal:

    Decoded Report: &{FeedId:0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 FeedVersion:3 V2Report:<nil> V3Report:0x140001b4c60 Round:11 Epoch:1919476 Digest:[0 6 249 181 83 227 147 206 211 17 85 30 253 48 209 222 206 219 99 215 106 212 23 55 70 46 44 219 189 255 21 120]}
    Valid From Timestamp: 1712557606
    Observations Timestamp: 1712557606
    Native Fee: 29191712479600
    Link Fee: 5655191262392300
    Expires At: 1712644006
    Benchmark Price: 3425629793731062400000
    Bid: 3425455500000000000000
    Ask: 3425747100000000000000
    

Decoded report details

The decoded report details include:

AttributeValueDescription
Feed ID0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782The unique identifier for the Data Streams feed. In this example, the feed is ETH/USD on Arbitrum Sepolia with the Basic Report schema.
Feed Version3The feed report schema version.
Valid From Timestamp1712557606The start validity timestamp for the report, indicating when the data becomes relevant.
Observations Timestamp1712557606The timestamp indicating when the data was captured.
Native Fee29191712479600The fee to pay in the native blockchain token (e.g., testnet ETH on Arbitrum Sepolia) for the onchain verification of the report data. With 18 decimals. Note: This example fee is not indicative of actual fees.
Link Fee5655191262392300The fee to pay in LINK tokens for the onchain verification of the report data. With 18 decimals. For readability: 0.005655191262 LINK. Note: This example fee is not indicative of actual fees.
Expires At1712644006The expiration timestamp of the report, indicating the point at which the data becomes outdated.
Benchmark Price3425629793731062400000The observed price in the report, with 18 decimals. For readability: 3,425.6297937310624 ETH per USD.
Bid3425455500000000000000The simulated price impact of a buy order up to the X% depth of liquidity usage. For readability: 3,425.4555 ETH per USD.
Ask3425747100000000000000The simulated price impact of a sell order up to the X% depth of liquidity usage. For readability: 3,425.7471 ETH per USD.

Verify report data onchain

In this tutorial, you log and decode the FullReport payloads to extract the reports data. In a production environment, you should verify the data onchain to ensure its integrity and authenticity. Refer to the Verify report data onchain guide.

Explanation

Establishing a WebSocket connection and listening for message reports

The ConnectAndListen function in the client package initializes the connection and enables the application to listen for messages from the Data Streams Aggregation Network via WebSocket.

  • Authentication and security: Uses HMAC headers, created with CLIENT_ID and CLIENT_SECRET, for secure, authenticated communication.
  • Continuous listening: After authentication, continuously listens for real-time report messages, which include the feedId and the encoded FullReport.

Decoding a report

The DecodeFullReportAndReportData function in the internal package unpacks the FullReport payload, decodes the FullReport's ReportBlob, and decodes the report data into a structured format:

  1. It unpacks the FullReport payload that includes the report context, the report blob and raw signatures:

    • Report context: The report context contains metadata such as the feedId and the report version.
    • Cryptographic signatures: These signatures validate the data's integrity and authenticity.
  2. Report data decoding: The function proceeds to decode the report data into a structured format.

Handling the decoded data

In this example, the application logs the structured report data to the terminal. However, this data can be used for further processing, analysis, or display in your own application.

What's next

Get the latest Chainlink content straight to your inbox.