> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://partner.ninjatrader.com/eval/overview/conformance-testing/stage-2-websocket-management/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://partner.ninjatrader.com/_mcp/server. # Stage 2: WebSocket Management WebSocket connections are essential for real-time data streaming and order management in the NinjaTrader Partner API. This stage validates your integration's ability to maintain stable WebSocket connections and handle real-time events. ## Overview The WebSocket management stage ensures your application can: * Establish and maintain WebSocket connections * Handle authentication over WebSocket * Process real-time events * Manage connection heartbeats and reconnection logic * Handle rate limiting and synchronous requests ## Required Tests ### 1. WebSocket Login **Purpose:** Authenticate WebSocket connection using access token. **Test Steps:** 1. Establish WebSocket connection to `wss://demo.tradovateapi.com/v1/websocket` 2. Send authentication message with valid access token 3. Verify successful authentication response 4. Test with invalid/expired token to ensure proper error handling **Example Implementation:** ```javascript const ws = new WebSocket('wss://demo.tradovateapi.com/v1/websocket'); ws.onopen = function() { // Send authentication message const authMessage = `authorize\n0\n\n${accessToken}`; ws.send(authMessage); }; ws.onmessage = function(event) { const data = JSON.parse(event.data); if (data.i === 0) { // Authentication response console.log('Authentication successful:', data); } }; ``` **Expected Authentication Response:** ```json { "i": 0, "s": 200 } ``` **Expected Error Response:** ```json { "i": 0, "s": 401 } ``` **Validation Criteria:** * ✅ WebSocket connection establishes successfully * ✅ Authentication succeeds with valid token * ✅ Authentication fails with invalid token * ✅ Connection remains stable after authentication ### 2. Heartbeats **Purpose:** Maintain connection health and detect disconnections. **Test Steps:** 1. Send periodic heartbeat messages 2. Verify heartbeat responses are received 3. Test heartbeat timeout scenarios 4. Verify connection recovery after missed heartbeats **Example Implementation:** ```javascript // Track last message time let lastMessageTime = new Date().getTime(); ws.onmessage = function(event) { const timestamp = new Date().getTime(); if(timestamp - lastMessageTime > 2500) { ws.send('[]'); // Empty frame is a heartbeat in our protocol lastMessageTime = timestamp; // Update last message time } // carry on with message handling // ... }; ``` **Validation Criteria:** * ✅ Heartbeats are sent at regular intervals * ✅ Heartbeat mechanism works during high message volume ### 3. Event Handling **Purpose:** Process real-time events from the WebSocket. **Test Steps:** 1. Manage real-time subscriptions * Start event streaming using `user/syncrequest` * subscribe to relevant entity types using the `entityTypes` parameter 2. Process incoming events **Example `user/syncrequest`:** ```javascript // Connect to the websocket const ws = new WebSocket('wss://demo.tradovateapi.com/v1/websocket'); const requestBody = { splitResponses: true, // Optional, define sharding expression shardingExpression: { expressionType: 'modUserId', // | 'modAccountId' divisor: 1, // each instance should have the same divisor remainder: 0, // modulus of the ID of user or account to process on this instance }, entityTypes: [ 'account', 'accountRiskStatus', 'cashBalance', 'commandReport', 'command', 'executionReport', 'fill', 'fillPair', 'order', 'orderStrategy', 'position', 'product', 'user' ], } ws.onopen = function() { ws.send(`authorize\n0\n\n${accessToken}`); } ws.onmessage = function(event) { const type = event.data.slice(0, 1); let data = null; if(type === 'a') { data = JSON.parse(event.data.slice(1)); } if(data.i === 0) { // Authentication response console.log('Authentication successful:', data); // Send syncrequest ws.send(`user/syncrequest\n1\n\n${JSON.stringify(requestBody)}`); } } ws.onerror = function(event) { console.error('WebSocket error:', event); } ``` **Validation Criteria:** * ✅ Only one `syncrequest` is sent per socket lifecycle. * ✅ Real-time events are received * ✅ Sharding is implemented correctly * ✅ Entity types are subscribed to correctly * ✅ Syncrequest is sent correctly * ✅ Event handlers are robust and don't crash on malformed data ### 4. Rate Limit Handling (P-Ticket) **Purpose:** Handle rate limiting and priority ticket system. For what penalty tickets are, what triggers them, and how to recover, see [Penalty Tickets](/overview/core-concepts/penalty-tickets). **Test Steps:** 1. Send requests that trigger rate limiting 2. Verify P-Ticket system responses 3. Implement proper queuing and retry logic 4. Test priority handling for different request types **Example Implementation:** ```javascript const send = requestData => { return new Promise((res, rej) => { let i = ++lastId; let race = setTimeout(() => { reject(new Error('Request timeout'))}, 30000); function handler(event) { const type = event.data.slice(0, 1); if(type === 'a') { const data = JSON.parse(event.data.slice(1)); // data is always an array for(const datum of data) { if(datum.i === i) { if(datum.d && 'p-ticket' in datum.d) { const pTicket = datum.d['p-ticket']; const pTime = datum.d['p-time']; const pCaptcha = datum.d['p-captcha']; const pMessage = datum.d['p-message']; console.log(`Rate limit reason: ${pMessage}`); if(pCaptcha) { // Handle captcha, if you receive it you have been locked out for 1 hour console.error('p-captcha received. Operations locked for 1 hour.'); return; } else { // Handle rate limiting with P-Ticket, resend the request // with the p-ticket after p-time seconds. setTimeout(() => { send({ ...requestData, body: { ...requestData.body, 'p-ticket': pTicket } }); }, pTime * 1000); ws.removeEventListener('message', handler); } } else { clearTimeout(race); res(datum.d); ws.removeEventListener('message', handler); } } } } } // construct the message string const queryString = requestData.query || '' const bodyString = JSON.stringify(requestData.body || '') const messageString = `${requestData.endpoint}\n${i}\n${queryString}\n${bodyString}`; ws.addEventListener('message', handler); ws.send(messageString); }); } ``` **Validation Criteria:** * ✅ Rate limiting responses are properly handled * ✅ P-Ticket system is implemented correctly ### 5. Reconnection **Purpose:** Automatically reconnect after connection loss. **Test Steps:** 1. Simulate connection drops 2. Verify automatic reconnection logic 3. Test re-authentication after reconnection 4. Verify state recovery after reconnection **Example Implementation:** ```javascript let lastMessageTime = new Date().getTime(); ws.onmessage = async function(event) { const timestamp = new Date().getTime(); if(timestamp - lastMessageTime > 15000) { // connection has been idle for 15 seconds, reconnect await reconnect(); } lastMessageTime = timestamp; } ``` **Validation Criteria:** * ✅ Automatic reconnection works after connection loss * ✅ Re-authentication occurs after reconnection * ✅ State is properly recovered after reconnection ## Performance Requirements * **Heartbeat**: Send heartbeats every 2.5 seconds (2,500 ms) For detailed performance requirements, contact Evaluation Support. ## Testing Checklist * [ ] WebSocket connection establishes successfully * [ ] Authentication works with valid tokens * [ ] Heartbeats are sent and received * [ ] Events are processed correctly * [ ] Rate limiting is handled properly * [ ] Syncrequests work as expected and are called only once per socket lifecycle * [ ] Reconnection works automatically ## Next Steps After completing Stage 2 WebSocket management tests, proceed to [Stage 3: User Management](/overview/conformance-testing/stage-3-user-management).