Notification API WebSocket dropping connection after 30s despite keepalive

The docs state: “The client must send a keepalive message every 15 seconds to maintain the connection.”

I’m building a token service that needs to listen for real-time conversation events. I have the OAuth token sorted. The WebSocket handshake to wss://api.mypurecloud.com/v2/analytics/events succeeds. I get a 200 status.

I’m sending the keepalive payload exactly as specified:

{"type": "keepalive"}

Every 15 seconds. No deviation.

The server acknowledges it. But at exactly 30 seconds, the connection drops. No error frame. Just a clean close. I’ve checked the logs. Nothing suspicious. The client isn’t timing out on my end. It’s the server initiating the close.

Here’s the relevant snippet from my Node.js client:

ws.on('open', () => {
 ws.send(JSON.stringify({ type: 'keepalive' }));
 setInterval(() => {
 ws.send(JSON.stringify({ type: 'keepalive' }));
 }, 15000);
});

I’ve tried increasing the interval to 14s. Same result. I’ve tried sending it immediately after open. Same result.

Is there a hidden parameter I’m missing? The docs don’t mention any other requirements for the keepalive frame. I’ve validated the JSON structure. It’s valid.

Anyone else hit this wall?

You might be hitting a timeout on the server side because the keepalive isn’t actually being processed as a distinct frame or the interval is too aggressive for the gateway. The docs say 15 seconds, but I’ve found that sending it exactly at 15s sometimes trips up the load balancer if there’s any latency. Try bumping it to 12 seconds to be safe. Also, make sure you are sending the JSON string, not a raw object, and that the connection is using the correct subdomain for your org.

Here is how I usually structure the keepalive logic in Node.js. It’s a bit verbose but it helps to log exactly when the ping goes out and if you get a pong back. If you don’t see the pong, the issue is likely the endpoint or the token scope, not the keepalive itself.

const WebSocket = require('ws');
const ws = new WebSocket('wss://api.mypurecloud.com/v2/analytics/events');

ws.on('open', () => {
 console.log('Connected');
 // Start keepalive interval
 setInterval(() => {
 if (ws.readyState === WebSocket.OPEN) {
 const keepalive = JSON.stringify({ type: 'keepalive' });
 console.log('Sending keepalive:', keepalive);
 ws.send(keepalive);
 }
 }, 12000); // 12 seconds to be safe
});

ws.on('message', (data) => {
 console.log('Received:', data.toString());
});

ws.on('close', (code, reason) => {
 console.log(`Closed: ${code} - ${reason}`);
});

Also, check your OAuth scopes. The token needs analytics:events:read or similar. If the scope is wrong, the handshake might succeed but the server drops you shortly after because it can’t authorize the stream. I’ve seen this happen when switching between a user token and an app token. The user token often has more permissive scopes by default. If you are using an app token, make sure you added the right scopes in the developer portal. It’s an easy oversight.

One more thing. Are you connecting to the global endpoint or a specific region? If you are in Europe, you might want to hit api.eu.mypurecloud.com. Latency to the US endpoint can sometimes cause the keepalive to arrive late, leading to a drop. Try switching to the regional endpoint if you aren’t already. It usually stabilizes the connection.