Stream and decode Data Streams reports via WebSocket using the TypeScript SDK
Guide Versions
This guide is available in multiple versions. Choose the one that matches your needs.
In this tutorial, you'll learn how to use the Data Streams SDK for TypeScript to subscribe to real-time reports via a WebSocket connection. You'll set up your TypeScript 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 --versionin your terminal and download the latest version from the official Git website if necessary. - Node.js: Make sure you have Node.js 20.0 or higher. You can check your current version by running
node --versionin your terminal and download the latest version from the official Node.js website if necessary. - TypeScript: Make sure you have TypeScript 5.3 or higher. You can check your current version by running
npx tsc --versionin your terminal and install or update TypeScript by runningnpm install -g typescriptif necessary. - API Credentials: Access to Data Streams requires API credentials. If you haven't already, contact us to request mainnet or testnet access.
Tutorial
First, you'll set up a basic TypeScript project, installing the SDK and pasting example code. This will let you stream reports for streams, logging their attributes to your terminal.
Set up your TypeScript project
-
Create a new directory for your project and navigate to it:
mkdir my-data-streams-project cd my-data-streams-project -
Initialize a new Node.js project:
npm init -y -
Install the TypeScript SDK and other required packages:
npm install @chainlink/data-streams-sdk dotenv npm install -D tsx -
Set your API credentials:
Option 1 - Environment variables:
export API_KEY="your_api_key_here" export USER_SECRET="your_user_secret_here"Option 2 -
.envfile:# Create .env file touch .env # Add your credentials API_KEY="your_api_key_here" USER_SECRET="your_user_secret_here"
Establish a WebSocket connection and listen for real-time reports
-
Create a new TypeScript file,
stream.ts, in your project directory:touch stream.ts -
Insert the following code example and save your
stream.tsfile:import { createClient, LogLevel, decodeReport, getReportVersion, formatReport } from "@chainlink/data-streams-sdk" import "dotenv/config" async function main() { if (process.argv.length < 3) { console.error("Please provide one or more feed IDs as arguments") console.error("\nExamples:") console.error(" Single feed:") console.error(" npx tsx stream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782") console.error(" Multiple feeds:") console.error( " npx tsx stream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782,0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265" ) process.exit(1) } const feedIds = process.argv[2].split(",") console.log("Chainlink Data Streams - Report Streaming") console.log("=".repeat(60)) console.log(`๐ Feeds: ${feedIds.length} feed(s)`) console.log("=".repeat(60)) try { const client = createClient({ apiKey: process.env.API_KEY || "YOUR_API_KEY", userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET", endpoint: "https://api.testnet-dataengine.chain.link", wsEndpoint: "wss://ws.testnet-dataengine.chain.link", // Comment to disable SDK logging: logging: { logger: console, logLevel: LogLevel.INFO, enableConnectionDebug: false, // Enable WebSocket ping/pong and connection state logs (logLevel should be DEBUG) }, }) let reportCount = 0 // Create stream with custom options const stream = client.createStream(feedIds, { maxReconnectAttempts: 10, reconnectInterval: 3000, }) // Event: Process incoming reports stream.on("report", (report) => { reportCount++ try { console.log(`\n๐ Report #${reportCount} - ${new Date().toISOString()}`) // Show raw report blob console.log(`\nRaw Report Blob: ${report.fullReport}`) // Decode the report const decodedData = decodeReport(report.fullReport, report.feedID) const version = getReportVersion(report.feedID) // Combine decoded data with report metadata const decodedReport = { ...decodedData, feedID: report.feedID, validFromTimestamp: report.validFromTimestamp, observationsTimestamp: report.observationsTimestamp, } console.log(formatReport(decodedReport, version)) } catch (error) { console.error(`โ Error processing report: ${error instanceof Error ? error.message : error}`) } // Display stats every 5 reports if (reportCount % 5 === 0) { const stats = stream.getMetrics() console.log( `\n๐ Stats: ${stats.accepted} reports | ${stats.activeConnections}/${stats.configuredConnections} connections` ) } }) // Event: Handle errors stream.on("error", (error) => { console.error(`\nโ Error: ${error.message}`) if (error.message.includes("authentication")) { console.error("๐ก Check your API_KEY and USER_SECRET environment variables") } }) // Event: Handle disconnections stream.on("disconnected", () => { console.log("\n๐ด Stream disconnected - reconnecting...") }) // Event: Monitor reconnections stream.on("reconnecting", (info: { attempt: number; delayMs: number; origin?: string; host?: string }) => { console.log( `๐ Reconnecting... attempt ${info.attempt} in ~${info.delayMs}ms${info.host ? ` (${info.host})` : ""}` ) }) console.log("โณ Connecting...\n") await stream.connect() console.log("โ Connected! Listening for reports...\n") // Graceful shutdown const shutdown = async () => { console.log("\n๐ Shutting down...") await stream.close() console.log("โ Shutdown complete") process.exit(0) } process.on("SIGINT", shutdown) process.on("SIGTERM", shutdown) } catch (error) { console.error("โ Failed to start stream:", error instanceof Error ? error.message : error) process.exit(1) } } main() -
Subscribe to a testnet crypto stream. The below example executes the application, subscribing to the
ETH/USDcrypto stream:npx tsx stream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782Expect output similar to the following in your terminal:
Chainlink Data Streams - Report Streaming ============================================================ ๐ Feeds: 1 feed(s) ============================================================ [2025-09-24T01:52:36.464Z] [DataStreams] Data Streams client initialized [2025-09-24T01:52:36.465Z] [DataStreams] Initializing stream in single mode [2025-09-24T01:52:36.465Z] [DataStreams] Stream created successfully for 1 feed(s) โณ Connecting... [2025-09-24T01:52:36.465Z] [DataStreams] Connecting stream in single mode [2025-09-24T01:52:36.465Z] [DataStreams] Initializing in single connection mode { origin: 'ws.testnet-dataengine.chain.link' } [2025-09-24T01:52:36.919Z] [DataStreams] Connection conn-0 established to ws.testnet-dataengine.chain.link { connectionId: 'conn-0', host: 'ws.testnet-dataengine.chain.link', oldState: 'connecting', newState: 'connected', reason: 'WebSocket connection established' } [2025-09-24T01:52:36.921Z] [DataStreams] Stream connected successfully with 1 origins: wss://ws.testnet-dataengine.chain.link โ Connected! Listening for reports... ๐ Report #1 - 2025-09-24T01:52:37.639Z Raw Report Blob: 0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001fb0b3d000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d34ee50000000000000000000000000000000000000000000000000000000068d34ee500000000000000000000000000000000000000000000000000004566c7d15bf000000000000000000000000000000000000000000000000000346f5bdd1b7f7f0000000000000000000000000000000000000000000000000000000068fadbe50000000000000000000000000000000000000000000000e3535f4d688742fe400000000000000000000000000000000000000000000000e35216a3ce81ab00000000000000000000000000000000000000000000000000e355718e650ceb8c00000000000000000000000000000000000000000000000000000000000000000264deb1e4d7485843f79f802f8ffd29fa395c45a8f4c10d7771d91fb5c6c7f55bdf3b496949277bdba413cf9a07adf62d4ffac8d04572455c8707a578eec7988f000000000000000000000000000000000000000000000000000000000000000221d03f390a3a8bf14406a5026bca8a569908abd0e3bfc1152e0ee94cf8087b29648cffd6e505f392678ad3c2488862dc6016755aec3076e64fa68d13705a5980 Report Metadata: Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 Valid From: 1758678757 Observations: 1758678757 Decoded Data: Native Fee: 76307741367280 LINK Fee: 14759139131228031 Expires At: 1761270757 Price: 4193418510271345000000 Bid Price: 4193326000000000000000 Ask Price: 4193567763462320000000 -------------------------------------------------- ๐ Report #2 - 2025-09-24T01:52:38.352Z Raw Report Blob: 0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001fb0b3f000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d34ee60000000000000000000000000000000000000000000000000000000068d34ee600000000000000000000000000000000000000000000000000004567035aadb500000000000000000000000000000000000000000000000000346f5006b572370000000000000000000000000000000000000000000000000000000068fadbe60000000000000000000000000000000000000000000000e354601f8dd7842d400000000000000000000000000000000000000000000000e35343330ef5aa80000000000000000000000000000000000000000000000000e3576b7b7e0d95bc0000000000000000000000000000000000000000000000000000000000000000029bbebb9390dfd84074ef0078b3b354a8c3f53915d24b664095b4360b20e495cfc1ee156fb32ffae29390a891b905b96fec0e949dbca19b7eded60a2178a0b7570000000000000000000000000000000000000000000000000000000000000002119306af79fb631fe3d281475b32e9e2c293fd6b544d4f5d44d74d9827d8897729c2bc6d5df3033d6b90aeb22df31f484f5ed7da9602c790213d3e12144544e1 Report Metadata: Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 Valid From: 1758678758 Observations: 1758678758 Decoded Data: Native Fee: 76308740222389 LINK Fee: 14759088289575479 Expires At: 1761270758 Price: 4193490798923085000000 Bid Price: 4193410600000000000000 Ask Price: 4193710169017200000000 -------------------------------------------------- ๐ Report #3 - 2025-09-24T01:52:39.274Z Raw Report Blob: 0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001fb0b42000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d34ee70000000000000000000000000000000000000000000000000000000068d34ee70000000000000000000000000000000000000000000000000000456689e0e86300000000000000000000000000000000000000000000000000346d7b2d006b3a0000000000000000000000000000000000000000000000000000000068fadbe70000000000000000000000000000000000000000000000e355ee07f0ab7f00000000000000000000000000000000000000000000000000e353c9352421f6e2000000000000000000000000000000000000000000000000e3565e1f7be6e49700000000000000000000000000000000000000000000000000000000000000000245ddf834b20cdd13c559fdd131c27f3c52d38ba6a456d6064a64269b703bc2012d0ffbb4ab3df623c0bdfba83004ef4db50ea351314b648153d2f6002f78cb4e00000000000000000000000000000000000000000000000000000000000000026a6bbed7e57969d56e6485682afc481f8f8127f292e4bd300f6bb0a57b77b21a2ca0e3338ba16b0afbbae567fc4a728c4b95c2092169f412ae7b8fc53c81342a Report Metadata: Feed ID: 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 Valid From: 1758678759 Observations: 1758678759 Decoded Data: Native Fee: 76306702198883 LINK Fee: 14757074592361274 Expires At: 1761270759 Price: 4193602800000000000000 Bid Price: 4193448319936840000000 Ask Price: 4193634351084156000000 -------------------------------------------------- [...additional reports...]Your application has successfully subscribed to the report data.
Decoded report details
The decoded report details include:
| Attribute | Value | Description |
|---|---|---|
Stream ID | 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782 | The unique identifier for the stream. In this example, the stream is for ETH/USD. |
Observations Timestamp | 1758588884 | The timestamp indicating when the data was captured. |
Benchmark Price | 4195298740000000000000 | The observed price in the report, with 18 decimals. For readability: 4,195.29874 USD per ETH. |
Bid | 4194910430000000000000 | The highest price a buyer is willing to pay for an asset, with 18 decimals. For readability: 4,194.91043 USD per ETH. Learn more about the Bid price. (For DEX State Price streams, this value equals Benchmark Price.) |
Ask | 4195448050000000000000 | The lowest price a seller is willing to accept for an asset, with 18 decimals. For readability: 4,195.44805 USD per ETH. Learn more about the Ask price. (For DEX State Price streams, this value equals Benchmark Price.) |
Valid From Timestamp | 1758588884 | The start validity timestamp for the report, indicating when the data becomes relevant. |
Expires At | 1761180884 | The expiration timestamp of the report, indicating the point at which the data becomes outdated. |
Link Fee | 14805490063735767 | The fee to pay in LINK tokens for the onchain verification of the report data, with 18 decimals. For readability: 0.014805490063735767 LINK. Note: This example fee is not indicative of actual fees. |
Native Fee | 76275680556912 | The fee to pay in the native blockchain token (e.g., ETH on Ethereum) for the onchain verification of the report data, with 18 decimals. Note: This example fee is not indicative of actual fees. |
For descriptions and data types of other report schemas, see the Report Schema Overview.
Subscribing to multiple streams
You can subscribe to multiple streams by providing additional stream IDs as command-line arguments:
npx tsx stream.ts 0x000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba782,0x00036fe43f87884450b4c7e093cd5ed99cac6640d8c2000e6afc02c8838d0265
This will subscribe to both ETH/USD and BTC/USD streams.
High Availability (HA) mode
The example above demonstrates streaming data from a single crypto stream. For production environments, especially when subscribing to multiple streams, it's recommended to enable High Availability (HA) mode.
High Availability (HA) mode creates multiple WebSocket connections to different origin endpoints for improved reliability. When HA mode is enabled, the Stream will maintain at least 2 concurrent connections to different instances to ensure high availability, fault tolerance and minimize the risk of report gaps.
Enabling HA mode
To enable HA mode in your streaming application, make these changes to the basic example:
// ... existing code ...
const client = createClient({
apiKey: process.env.API_KEY || "YOUR_API_KEY",
userSecret: process.env.USER_SECRET || "YOUR_USER_SECRET",
endpoint: "https://api.dataengine.chain.link", // Mainnet endpoint
wsEndpoint: "wss://ws.dataengine.chain.link", // Single endpoint (mainnet only)
haMode: true, // Enable High Availability mode
// Optional: Advanced connection monitoring with origin tracking
connectionStatusCallback: (isConnected, host, origin) => {
const timestamp = new Date().toISOString().substring(11, 19)
const status = isConnected ? "๐ข UP" : "๐ด DOWN"
console.log(`[${timestamp}] ${status} ${host}${origin || ""}`)
// Example: Send alerts for specific origins
if (!isConnected && origin) {
console.warn(`โ ๏ธ Alert: Origin ${origin} on ${host} went offline`)
}
},
logging: {
logger: console,
logLevel: LogLevel.INFO,
},
})
// ... existing code ...
When haMode is true, the SDK automatically discovers multiple origin endpoints behind the single URL and establishes separate connections to each origin. You also must use a mainnet endpoint, as HA mode is not currently supported on testnet.
The optional connectionStatusCallback can be used to integrate with external monitoring systems. The SDK already provides comprehensive connection logs, so this callback is primarily useful for custom alerting or metrics collection.
See more details about HA mode in the SDK Reference.
Payload for onchain verification
The fullReport field in each streamed report is the signed payload you pass directly to VerifierProxy.verify() (or verifyBulk()) for onchain verification. No transformation or re-encoding is needed โ the raw bytes from the stream are the input.
From the example output above, the fullReport for Report #1 looks like this:
0x00090d9e8d96765a0c49e03a6ae05c82e8f8de70cf179baa632f18313e54bd690000000000000000000000000000000000000000000000000000000001fb0b3d000000000000000000000000000000000000000000000000000000030000000100000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000028001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000359843a543ee2fe414dc14c7e7920ef10f4372990b79d6361cdc0dd1ba7820000000000000000000000000000000000000000000000000000000068d34ee50000000000000000000000000000000000000000000000000000000068d34ee500000000000000000000000000000000000000000000000000004566c7d15bf000000000000000000000000000000000000000000000000000346f5bdd1b7f7f0000000000000000000000000000000000000000000000000000000068fadbe50000000000000000000000000000000000000000000000e3535f4d688742fe400000000000000000000000000000000000000000000000e35216a3ce81ab00000000000000000000000000000000000000000000000000e355718e650ceb8c00000000000000000000000000000000000000000000000000000000000000000264deb1e4d7485843f79f802f8ffd29fa395c45a8f4c10d7771d91fb5c6c7f55bdf3b496949277bdba413cf9a07adf62d4ffac8d04572455c8707a578eec7988f000000000000000000000000000000000000000000000000000000000000000221d03f390a3a8bf14406a5026bca8a569908abd0e3bfc1152e0ee94cf8087b29648cffd6e505f392678ad3c2488862dc6016755aec3076e64fa68d13705a5980
This payload is ABI-encoded and contains the report header, the DON-signed report body, and the signatures used for onchain verification. You do not need to decode or inspect it before passing it to the Verifier โ the VerifierProxy handles all of that.
In TypeScript, report.fullReport holds this value and can be passed directly to your verification contract:
// report.fullReport is the value to pass as `payload` to VerifierProxy.verify()
const decodedData = decodeReport(report.fullReport, report.feedID)
Refer to the Verify report data onchain tutorial for a step-by-step walkthrough of the full onchain verification flow.
Explanation
Establishing a WebSocket connection and listening for reports
Your application uses the createClient function from the Data Streams SDK to create a client, then uses client.createStream() to establish a real-time WebSocket connection with the Data Streams Aggregation Network.
Once the WebSocket connection is established, your application subscribes to one or more streams by passing an array of feed IDs to the createStream function. This subscription lets the client receive real-time updates whenever new report data is available for the specified streams.
Fore further reference, see the WebSocket Interface section of the SDK Reference.
Event-driven streaming
The TypeScript SDK uses an event-driven approach for handling streaming data:
- Connection events: The stream emits
connected,disconnected, andreconnectingevents to track connection status. - Report events: When new reports arrive, the
reportevent is triggered with the decoded report data. - Error handling: The stream emits
errorevents for any issues that occur during streaming. - Metrics: The
getMetrics()method provides real-time statistics about the stream performance.
Decoding and processing reports
As data reports arrive via the established WebSocket connection, they are processed in real-time:
- Automatic decoding: The SDK's
decodeReportfunction automatically detects the report version and parses the raw data into a structured format. - Real-time processing: Each received report triggers the
reportevent handler, where you can process, log, or store the decoded data. - Error resilience: Individual report processing errors don't interrupt the stream, allowing continuous operation.
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. The decoded data includes essential information such as benchmark prices, bid/ask spreads, and fee data for onchain verification.
For more information about SDK streaming configuration and advanced options, see the SDK Reference.