Sui's WebSocket subscription interface allows clients to receive real-time updates for events, checkpoints, and epochs. This guide explains the subscription flow, provides runnable TypeScript examples, and covers best practices for reconnection, rate limits, and using GraphQL as an alternative.
Direct Answer: What Are Sui WebSocket Subscriptions?
Sui's WebSocket subscription interface lets you receive real-time updates from the network without polling. You can subscribe to three main types of data: events (e.g., coin transfers, NFT mints), checkpoints (committed transaction batches), and epoch changes (network reconfiguration). The subscription uses the standard JSON-RPC 2.0 protocol over WebSocket, and the Sui TypeScript SDK provides a convenient wrapper. This guide walks you through the mechanics, provides runnable code, and shares best practices for production use.
If you're looking for a quick answer: connect to a WebSocket endpoint (e.g., wss://rpc.testnet.sui.io), send a subscription request with a method like suix_subscribeEvent, and listen for notifications. The server pushes messages with a params.result field containing the event data. You must handle reconnections manually, as the subscription does not survive network drops. For a managed RPC service that handles these complexities, see our Sui network page.
Understanding Sui's Subscription Architecture
Sui's WebSocket subscription is built on the JSON-RPC 2.0 protocol. The client sends a subscribe request with a method name and parameters. The server responds with a subscription ID. From then on, the server sends notification messages (JSON-RPC requests with no id) that include the subscription ID and the event data. To unsubscribe, you send an unsubscribe request with the subscription ID.
The subscription methods are grouped under the suix_ namespace. The primary methods are: suix_subscribeEvent (for events), suix_subscribeCheckpoint (for checkpoints), and suix_subscribeEpoch (for epoch changes). These are experimental in some Sui versions, meaning the API may change. Always check the Sui RPC best-practices documentation for the latest status.
The WebSocket endpoint is typically the same host as the HTTP RPC but with wss:// instead of https://. For example, if your RPC URL is https://rpc.testnet.sui.io, the WebSocket URL is wss://rpc.testnet.sui.io. OnFinality provides WebSocket support for Sui; see our RPC Assistant for endpoint details.
- Subscription flow: request -> subscription ID -> notifications -> unsubscribe
- Methods:
suix_subscribeEvent,suix_subscribeCheckpoint,suix_subscribeEpoch - Experimental status: API may change without notice
Prerequisites and Setup
To run the examples, you need Node.js (v16 or later) and npm. Install the Sui TypeScript SDK and the ws package for raw WebSocket examples. If you prefer a raw client, you can use any WebSocket library. The examples below use the official SDK for clarity.
You also need a Sui RPC endpoint. You can use a public endpoint like wss://rpc.testnet.sui.io or a dedicated endpoint from OnFinality. For production, consider a dedicated endpoint to avoid rate limits; see our pricing for options.
npm install @mysten/sui.js ws
# or if you use the latest SDK (as of 2026):
npm install @mysten/suiRunnable Example: Subscribing to Events with the Sui TypeScript SDK
The following example connects to Sui testnet, subscribes to all events, and prints the first 5 events. It uses the JsonRpcProvider from the SDK. Note that the SDK's subscribeEvent method returns a promise that resolves to an unsubscribe function.
The event payload shape includes id (event ID), type (event type string), sender (address), timestampMs, and parsedJson (the event-specific data). The exact fields depend on the event type. For example, a 0x2::coin::CoinBalanceChange event has coinType, amount, and owner fields.
import { JsonRpcProvider, testnetConnection } from '@mysten/sui.js';
const provider = new JsonRpcProvider(testnetConnection);
async function subscribeToEvents() {
const unsubscribe = await provider.subscribeEvent({
filter: { All: [] }, // subscribe to all events
onMessage: (event) => {
console.log('Received event:', JSON.stringify(event, null, 2));
},
});
// Unsubscribe after 10 seconds
setTimeout(async () => {
await unsubscribe();
console.log('Unsubscribed');
process.exit(0);
}, 10000);
}
subscribeToEvents().catch(console.error);Runnable Example: Raw WebSocket Client for Checkpoint Subscriptions
If you prefer a raw WebSocket client, you can use the ws package. This example subscribes to checkpoint notifications and prints the checkpoint sequence number. The request format follows JSON-RPC 2.0: { "jsonrpc": "2.0", "id": 1, "method": "suix_subscribeCheckpoint", "params": [] }.
The server will respond with { "jsonrpc": "2.0", "id": 1, "result": "<subscription_id>" }. Then, each notification will look like { "jsonrpc": "2.0", "method": "suix_subscribeCheckpoint", "params": { "subscription": "<subscription_id>", "result": { "sequenceNumber": "123", "timestampMs": "...", ... } } }.
const WebSocket = require('ws');
const ws = new WebSocket('wss://rpc.testnet.sui.io');
ws.on('open', () => {
console.log('Connected');
ws.send(JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'suix_subscribeCheckpoint',
params: [],
}));
});
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (msg.id === 1) {
console.log('Subscription ID:', msg.result);
} else if (msg.method === 'suix_subscribeCheckpoint') {
console.log('Checkpoint:', msg.params.result.sequenceNumber);
}
});
ws.on('error', (err) => console.error('WebSocket error:', err));
// Close after 15 seconds
setTimeout(() => ws.close(), 15000);Expected Event Payload Shape and How to Verify
When you subscribe to events, the notification's params.result is an object with the following structure (based on Sui's RPC schema): { "id": { "txDigest": "...", "eventSeq": "..." }, "type": "0x2::coin::CoinBalanceChange", "sender": "0x...", "timestampMs": "...", "parsedJson": { ... } }. The parsedJson varies by event type.
To verify your subscription is working, you can trigger a transaction (e.g., transfer SUI) and see the corresponding event. Alternatively, you can compare the event sequence with the checkpoint sequence. For a full list of event types, see the Sui documentation.
If you are using OnFinality's RPC service, you can monitor your usage and connection health via the API service dashboard.
Common Failures and Fixes: Reconnection and Rate Limits
WebSocket connections are inherently unstable. Network drops, server restarts, or idle timeouts can disconnect you. When the connection drops, your subscription is lost. You must reconnect and resubscribe. The Sui SDK does not automatically reconnect, so you need to implement a reconnection loop with exponential backoff.
Another common issue is hitting rate limits. Public endpoints often limit the number of subscriptions or messages per second. If you exceed the limit, the server may close the connection or return an error. To avoid this, use a dedicated endpoint or reduce the number of subscriptions. OnFinality provides scalable RPC services; see our Sui RPC guide for details.
For a detailed guide on handling WebSocket disconnections, see our article on WebSocket RPC disconnection fixes.
- Implement reconnection with exponential backoff and resubscribe to all active subscriptions.
- Monitor connection health with ping/pong frames.
- Use a dedicated endpoint to avoid rate limits.
Tradeoffs and Limitations: WebSocket vs. GraphQL vs. Polling
WebSocket subscriptions provide low-latency push updates, but they require persistent connections and manual reconnection logic. Polling is simpler but introduces latency and extra load. Sui also offers a GraphQL API that supports subscriptions (via WebSocket) and queries. GraphQL is more flexible for complex queries but has a learning curve.
The WebSocket subscription interface is experimental in some Sui versions, so it may change. For production, consider using GraphQL subscriptions if you need stability. However, for simple event streaming, WebSocket is efficient.
OnFinality supports both WebSocket and GraphQL for Sui; see our network page for available endpoints.
Next Steps and Further Reading
Now that you understand Sui WebSocket subscriptions, you can build real-time applications like transaction monitors, NFT trackers, or analytics dashboards. Start with the examples above and adapt them to your use case.
For more advanced topics, explore the Sui RPC best-practices documentation and our learning hub. If you need a reliable RPC service, consider OnFinality's API service or pricing for dedicated endpoints.
If you encounter issues, our RPC Assistant provides troubleshooting tips. And don't forget to check our WebSocket disconnection fixes for robust connection handling.
Subscription Lifecycle and Message Framing
When you establish a WebSocket subscription with Sui, the client and server engage in a specific message exchange that is crucial to understand for robust implementation. After sending a subscription request (e.g., {"jsonrpc":"2.0","id":1,"method":"suix_subscribeEvent","params":[...]}), the server responds with a confirmation message that includes a subscription ID. This ID is unique to that subscription and is used in subsequent notifications and for unsubscribing. The confirmation message has the shape {"jsonrpc":"2.0","id":1,"result":"subscriptionId"}. Once subscribed, the server pushes event notifications as separate messages, each with the structure {"jsonrpc":"2.0","method":"suix_subscribeEvent","params":{"subscription":"subscriptionId","result":{...}}}. The result field contains the actual event data. It's important to note that the method field in the notification echoes the subscription method, not the JSON-RPC method used for the request. This allows the client to route notifications to the correct handler when multiple subscriptions are active.
Error handling in the subscription lifecycle is often overlooked. If a subscription fails (e.g., due to invalid parameters or permission issues), the server sends an error response with the same id as the request, following the standard JSON-RPC error format. However, after a subscription is established, errors can also occur asynchronously. For example, if the server encounters an internal error while processing events, it may send a notification with a method field set to suix_subscribeEvent and an error field instead of result. Clients must be prepared to handle both success and error notifications. Additionally, the server may send a method notification with the method name suix_unsubscribeEvent to indicate that a subscription has been terminated (e.g., due to a server-side timeout). Properly handling these messages ensures your client can react gracefully to unexpected terminations.
- Always match the
idin the confirmation response to the request to correlate subscriptions. - Use the subscription ID from the confirmation to manage state and route incoming notifications.
- Handle both
resultanderrorfields in notifications; errors can occur after subscription is active. - Be aware that the server may send a termination notification; implement logic to resubscribe if needed.
// Example of handling subscription confirmation and notification
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.id !== undefined) {
// This is a response to a request (e.g., subscription confirmation)
if (msg.result) {
console.log('Subscribed with ID:', msg.result);
subscriptionId = msg.result;
} else if (msg.error) {
console.error('Subscription error:', msg.error);
}
} else if (msg.method) {
// This is a notification
if (msg.params && msg.params.subscription === subscriptionId) {
if (msg.params.result) {
console.log('Event received:', msg.params.result);
} else if (msg.params.error) {
console.error('Notification error:', msg.params.error);
}
}
}
});Scaling Subscriptions and Idempotent Event Handling
As your application grows, you may need to handle a high volume of events from multiple subscriptions. A common pattern is to multiplex many subscriptions over a single WebSocket connection. However, this introduces complexity in managing multiple subscription IDs and ensuring that events are processed correctly. One key best practice is to treat event handling as idempotent. Since network issues can cause duplicate deliveries, your event processing logic should be able to handle the same event multiple times without adverse effects. Sui events include a digest field (a base58-encoded hash) that uniquely identifies the event. By maintaining a set of recently processed digests (e.g., in a cache with a TTL), you can deduplicate events and avoid double-processing.
Another scaling consideration is the use of multiple WebSocket connections to distribute load. Sui RPC providers often impose per-connection limits on the number of subscriptions or the rate of notifications. By sharding subscriptions across multiple connections (e.g., based on event type or checkpoint range), you can increase throughput. However, this requires careful coordination to ensure that events are not missed or processed out of order. For checkpoint subscriptions, you can use the startCheckpoint parameter to resume from a specific checkpoint, but you must track the last processed checkpoint per connection. Additionally, consider using a message queue or a stream processing framework (like Apache Kafka or Redis Streams) to buffer events and decouple ingestion from processing. This allows you to scale processing horizontally without losing events.
- Use the event
digestto deduplicate events; store processed digests in a cache with a TTL. - Design event handlers to be idempotent—processing the same event twice should have no side effects.
- Consider sharding subscriptions across multiple WebSocket connections to bypass per-connection limits.
- Track the last processed checkpoint per connection to enable resumption after disconnects.
- Integrate a message queue to buffer events and decouple ingestion from processing for scalability.
// Example of deduplication using a Set with TTL
const processedDigests = new Map(); // digest -> timestamp
const TTL_MS = 60000; // 1 minute
function handleEvent(event) {
const digest = event.digest;
const now = Date.now();
// Clean up old entries
for (const [key, ts] of processedDigests) {
if (now - ts > TTL_MS) processedDigests.delete(key);
}
if (processedDigests.has(digest)) {
console.log('Duplicate event ignored:', digest);
return;
}
processedDigests.set(digest, now);
// Process event...
}Raw WebSocket Client Example for Subscriptions
While the Sui TypeScript SDK provides a convenient wrapper, understanding the raw WebSocket protocol is essential for debugging and for languages without SDK support. Below is a complete example using Node.js's ws library to subscribe to Sui events and receive notifications. This example demonstrates the full lifecycle: connecting, subscribing, handling notifications, and unsubscribing. It also includes error handling and a simple reconnection strategy.
The example subscribes to all events (using an empty filter) and logs the event type and digest. In practice, you would filter events to reduce noise and bandwidth. The code also shows how to send an unsubscribe request and close the connection gracefully. Note that the WebSocket URL is the standard Sui RPC endpoint; you can replace it with your provider's endpoint. For production, consider using a library like reconnecting-websocket to handle reconnections automatically, but this example provides a manual implementation for clarity.
- The example uses the
wslibrary; install it withnpm install ws. - The subscription request uses
suix_subscribeEventwith an empty filter to receive all events. - The
idfield in the request is used to match the confirmation response. - Notifications are identified by the
methodfield and contain the subscription ID. - Unsubscribe by sending
suix_unsubscribeEventwith the subscription ID.
const WebSocket = require('ws');
const ws = new WebSocket('wss://fullnode.mainnet.sui.io:443');
let subscriptionId = null;
let requestId = 1;
ws.on('open', () => {
console.log('Connected');
// Subscribe to all events
const subscribeMsg = {
jsonrpc: '2.0',
id: requestId++,
method: 'suix_subscribeEvent',
params: [
{
filter: {} // Empty filter means all events
}
]
};
ws.send(JSON.stringify(subscribeMsg));
});
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.id !== undefined) {
// Response to our request
if (msg.result) {
subscriptionId = msg.result;
console.log('Subscribed with ID:', subscriptionId);
} else if (msg.error) {
console.error('Subscription error:', msg.error);
}
} else if (msg.method === 'suix_subscribeEvent') {
// Notification
const { subscription, result } = msg.params;
if (subscription === subscriptionId) {
console.log('Event received:', result.type, result.digest);
}
}
});
ws.on('error', (err) => {
console.error('WebSocket error:', err);
});
ws.on('close', () => {
console.log('Connection closed');
// Reconnect logic here
});
// To unsubscribe later:
function unsubscribe() {
if (subscriptionId) {
const unsubMsg = {
jsonrpc: '2.0',
id: requestId++,
method: 'suix_unsubscribeEvent',
params: [subscriptionId]
};
ws.send(JSON.stringify(unsubMsg));
}
}Known Limitations and Assumptions
Sui's WebSocket subscription API is still evolving, and there are several limitations and assumptions you should be aware of when building applications. First, the subscription methods (e.g., suix_subscribeEvent, suix_subscribeCheckpoint) are not part of the stable JSON-RPC API and may change without notice. The Sui RPC best-practices documentation explicitly notes that subscriptions are experimental and subject to change. Therefore, you should pin your SDK version and monitor Sui's release notes for updates.
Second, providers may impose their own limits on WebSocket connections, such as maximum number of subscriptions per connection, message rate limits, or connection duration. For example, a provider might limit you to 100 subscriptions per connection or disconnect idle connections after 5 minutes. These limits are not standardized and can vary. Always check your provider's documentation and implement reconnection logic that respects these constraints. Third, event delivery is not guaranteed to be exactly-once; duplicates can occur, and events may be missed if the connection drops. You should design your system to tolerate at-least-once delivery and use checkpoint subscriptions for reliable replay. Finally, the subscription API currently does not support filtering by transaction digest or sender address directly; you must filter events client-side if needed. This can lead to high bandwidth usage if you subscribe to all events, so use filters wisely.
- Subscription APIs are experimental and may change; pin SDK versions and monitor updates.
- Provider-specific limits on connections, subscriptions, and rates are common; check documentation.
- Event delivery is at-least-once; duplicates are possible, and missed events can occur on disconnects.
- Use checkpoint subscriptions for reliable replay and to avoid missing events.
- Client-side filtering may be necessary for fine-grained event selection.