Skip to main content

Connection Guide

info

To use the ChainStream API you'll need to be subscribed to our Scale Mode plan and enable ChainStream in the platform.

WebSocket Endpoint

The ChainStream API endpoint is different than the Syndica Solana Mainnet RPC endpoint available on the platform.

wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY>

Valid URL and Method Name Combinations

warning

Rule of thumb: the string chainstream. should appear exactly once—either in the WebSocket host or in the request's method, never both.

WebSocket URLMethod Name
wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY><methodName> (no prefix)
wss://api.syndica.io/api-key/<YOUR_API_KEY>chainstream.<methodName> (prefix)

Example using the recommended URL: wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY>:

{
"jsonrpc": "2.0",
"id": 1,
"method": "transactionsSubscribe", // no prefix
"params": {
"network": "solana-mainnet",
"verified": true,
"filter": {
"excludeVotes": false,
"commitment": "processed"
}
}
}

If you switch to the non-prefixed api. sub-domain instead, simply add the chainstream. prefix to the request's method and you're good to go. An incorrect combination of the two will result in a method not found error.

Connection Example (Node.js)

Here's a complete example of connecting to ChainStream and subscribing to slot updates:

const url = 'wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY>'
const connection = new WebSocket(url)

const slotUpdatesSubscribeReq = {
jsonrpc: '2.0',
id: '123',
method: 'slotUpdatesSubscribe',
params: {
network: 'solana-mainnet',
},
}

connection.onopen = () => {
connection.send(JSON.stringify(slotUpdatesSubscribeReq))
}

connection.onerror = (error) => {
console.log(`WebSocket error: ${error}`)
}

connection.onmessage = (e) => {
console.log(e.data)
// {
// "jsonrpc": "2.0",
// "method": "slotUpdateNotification",
// "params": {
// "subscription": 7091,
// "result": {
// "context": {
// "nodeTime": "2025-10-02T20:30:00.151839555Z"
// },
// "value": {
// "slot": 370770515,
// "parent": 370770514,
// "status": "finalized"
// }
// }
// }
// }
}

WebSocket Compression (Per-Message Deflate)

ChainStream API supports the Per-Message Deflate extension for WebSocket connections. This allows supported clients to decompress messages received over the WebSocket connection, which can reduce bandwidth usage and improve latencies due to lower networking overhead.

tip

From our testing, enabling compression typically reduces the size of messages sent over the WebSocket connection by approximately 70-90%, especially for high-bandwidth streams such as transactions.

To enable compression, use the permessage-deflate extension when creating the WebSocket connection. Many WebSocket libraries support this extension by default, but you can also specify it explicitly if needed.

Example in Node.js using the ws library:

import WebSocket from 'ws'

const ws = new WebSocket(
'wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY>',
{
perMessageDeflate: true
}
)

ws.once("open", function open(): void {
console.log("connected to Chainstream API");
console.log("per-message deflate negotiated:", ws.extensions.includes("permessage-deflate"));

// Subscribe to transactions
ws.send(
JSON.stringify({
jsonrpc: "2.0",
id: 123,
method: "transactionsSubscribe",
params: {
network: "solana-mainnet",
verified: false,
filter: {
accountKeys: {
oneOf: ["675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"],
},
},
},
}),
);

// Send PING every 30s
setInterval(() => {
ws.ping();
}, 30000);
});

// Handle incoming messages as usual...

To read more about enabling Per-Message Deflate in your WebSocket client, see our WebSocket Compression guide.

Timeouts and Keep-Alive

Connection Timeout

There is a five (5) second timeout between creating a WebSocket connection (initial HTTP 101 connection upgrade request) and initiating a subscription (the actual JSON WebSocket RPC request).

Clients must send the JSON RPC request within this 5 second window after opening the connection. This is often taken care of for you by most WebSocket client implementations, but tools such as Postman separate out these stages for testing purposes.

Subscription Timeout

Once a subscription has been established, there is a sixty (60) second timeout for messages. In other words, if the subscription has no traffic over any 60 second window, the subscription is automatically closed.

Keep-Alive Required

To avoid automatic disconnection, clients must send PING messages periodically (for example, once every 30 seconds) to keep the subscription alive during periods with no notifications.

Complete Example with PING

import WebSocket from 'ws'

const wsUrl = 'wss://chainstream.api.syndica.io/api-key/<YOUR_API_KEY>'
const ws_transactions: WebSocket = new WebSocket(wsUrl)

var n_tx = 0

ws_transactions.on('open', function open(): void {
console.log('connected to Chainstream API')

// Send the specified request to subscribe.
// The request's filter below is intentionally set to produce no/very low volume of messages,
// keeping this subscription alive requires the client to send PING messages.
ws_transactions.send(
JSON.stringify({
jsonrpc: '2.0',
id: 123,
method: 'transactionsSubscribe',
params: {
network: 'solana-mainnet',
verified: false,
filter: {
accountKeys: {
all: [
'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY',
'675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8',
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
'SysvarRent111111111111111111111111111111111',
],
},
},
},
})
)

// Send PING messages every 30s.
setInterval(() => {
ws_transactions.ping()
}, 30000)
})

ws_transactions.on('message', function incoming(data: WebSocket.Data): void {
// The first message is always the subscription result.
if (n_tx == 0) {
console.log('transaction subscription result', JSON.parse(data.toString()))
} else {
// Deserialize.
let tx = JSON.parse(data.toString())

// Add a timestamp field and log out the transaction signature.
tx.utc_timestamp = new Date().toUTCString()
let sig = tx.params.result.context.signature
let ts = tx.utc_timestamp
console.log(`[${ts}] Transaction ${sig} received`)
}
n_tx++
})

ws_transactions.on('close', function close(): void {
console.log('Disconnected from the server.')
})

ws_transactions.on('error', function error(err: Error): void {
console.error('WebSocket error:', err)
})

Best Practices

  • Enable Compression - Always enable Per-Message Deflate compression for production applications, especially when subscribing to high-volume streams like transactions. This can reduce bandwidth by 70-90%.
  • Implement Keep-Alive - Send PING messages every 30 seconds to prevent automatic disconnection due to the 60-second inactivity timeout.
  • Handle Reconnection - Implement automatic reconnection logic with exponential backoff to handle network interruptions gracefully.
  • Save Subscription IDs (if needed) - When you receive a subscription response, save the subscription ID if you need to explicitly unsubscribe specific subscriptions. Disconnecting the WebSocket automatically unsubscribes all active subscriptions, so IDs are only required for selective unsubscription.
  • Monitor Connection State - Track connection state (connecting, connected, disconnected) and implement appropriate error handling for each state.

Error Reference

Error CodeMessageMeaningResolution
7429subscription method exceeded limitYou have more concurrent ChainStream streams than your plan permits (Scale includes 1 stream)Close unused streams or enable additional streams (see plans/pricing for costs)

FAQ and Troubleshooting

Can I have multiple subscriptions on one WebSocket connection?

Yes. A single connection can host hundreds of subscriptions (Standard Mode: up to 100 per connection; Scale Mode: up to 600). Each *Subscribe call returns a numeric ID—store it so you can *Unsubscribe later and keep the connection within limits. Reuse connections when possible to avoid extra handshakes, but remove idle subscriptions so they do not count toward your plan's method-specific caps (see Rate Limits and plans/pricing for details). If a connection closes, reconnect once and resubscribe using the IDs you tracked.

Why did my WebSocket connection close?

Connections close automatically when they violate one of our guardrails:

  • No active subscriptions: After the handshake we wait ~5 seconds for your client to create a subscription or send a keepalive. If nothing happens, the socket is recycled.
  • Inactivity timeout: If we do not observe traffic (notifications, client messages, or ping/pong) for 60 seconds, we close the connection. Send ping frames every 20-30 seconds to keep the session warm.
  • Credential or auth errors: Missing or invalid API keys (including revoked credentials) trigger immediate disconnects—check for 401 errors during the initial upgrade.
  • Connection limits exceeded: Standard Mode allows up to 100 concurrent WebSocket connections and 100 total subscriptions; Scale Mode raises those limits to 300 connections and 600 subscriptions. Method-specific caps also apply—see Rate Limits and plans/pricing for the full matrix.

Always implement reconnection logic with exponential backoff, resubscribe on reconnect, and see Error Handling plus Observability for guidance on interpreting disconnects.

Are there rate limits for WebSocket subscriptions?

Yes. Each plan defines ceilings for active WebSocket connections, total subscriptions, and per-method concurrency, plus optional per-IP enforcement. Review the WebSocket limits table for your tier, check plans/pricing for current allowances, and use custom rate limits to set credential-specific caps (connections, subscriptions, or per-method) when you need finer control.

How many concurrent ChainStream streams can I have?

Scale Mode includes one ChainStream subscription, and you can add up to nine additional subscriptions ($0.14/hour each) for a total of ten concurrent streams per account. Each subscription maps to one WebSocket connection that can host multiple event subscriptions. Configure custom rate limits for ChainStream per API key as needed, and review plans/pricing for the current limits by tier.

How do I reduce bandwidth usage?

Syndica automatically offers Per-Message Deflate compression (RFC 7692) on every WebSocket endpoint, typically reducing bandwidth by 70-90%. Most client libraries negotiate this extension automatically, but some require you to enable compression explicitly or install an additional dependency—check your client's docs to confirm it advertises permessage-deflate during the handshake.

For transaction-heavy streams you can further trim payloads by:

  • Setting metadataOnly: true to receive signatures, slots, and metadata without full transaction data.
  • Using excludeVotes: true to drop validator vote transactions.
  • Applying accountKeys filters to limit events to the accounts you care about.
When should I use ChainStream verified transactions?

Set verified: true when you need confirmation that multiple validators have processed a transaction before acting on it. This adds up to ~400 ms delay but provides additional correctness guarantees. Only use verified: true with commitment: "processed"; for confirmed or finalized, the network already provides the necessary consensus.

What commitment level should I use with ChainStream?

Choose based on your use case:

  • Processed: Fastest notifications, ideal for price feeds and applications that can tolerate potential rollbacks.
  • Confirmed: Balanced option with network consensus, good for most applications.
  • Finalized: Guaranteed by network finality, ~13 second delay, required for financial settlement.

Review the commitment levels documentation for deeper detail.

Can I filter ChainStream traffic by multiple account keys?

Yes. accountKeys filters support all (transaction must include all keys), oneOf (transaction must include at least one key), and exclude (transaction must not contain listed keys). Filters run in order: exclude, then all, then oneOf. Combine them to target just the transactions you care about. See the ChainStream API documentation for full examples.

Why is my ChainStream connection failing?

Common causes:

  • Scale Mode not enabled (ChainStream requires it—upgrade or review plans/pricing).
  • ChainStream not enabled on your dashboard's ChainStream API page.
  • No available streams / Error 7429 (subscription method exceeded limit) (all subscriptions in use). Close unused streams or enable additional streams—see plans/pricing for details.
  • API key missing ChainStream permissions.

Check the ChainStream API page for subscription status and limits after resolving the above.

What happens if a validator goes offline when using ChainStream?

ChainStream aggregates data from multiple validators and automatically routes around node failures, so you keep receiving events from healthy validators without interruption—one of the primary benefits over single-node WebSocket connections.