Node.js WebSocket consumer dropping Analytics events on reconnect

Can anyone clarify why my Node.js consumer silently drops Analytics Notification events immediately after a WebSocket reconnect?

I am building a security audit pipeline that streams Genesys Cloud Notification API events to a Kafka topic. The goal is to capture every user.login and user.logout event for real-time alerting. I am using the ws library in a Node.js 18 environment. The initial connection works fine. I subscribe to the analytics:notification channel. I receive the first batch of events. However, when I simulate a network drop and reconnect, the subscription seems to persist, but the event payload structure changes slightly, causing my parser to fail. I am getting undefined for the eventType field on the second batch. I have verified the OAuth token is valid. I am using a fresh access token from the /oauth/token endpoint. The token has the view:notification scope. I am not seeing any 401 Unauthorized errors. The issue appears to be in the event deserialization logic. I suspect the WebSocket reconnection handshake might be sending a stale subscription ID. I am resetting the subscription on every open event. I am not using the official SDK for the WebSocket part because I need raw control over the reconnection logic for my audit requirements. I am using the REST API for token management. I am confident the Kafka producer is working. The issue is strictly in the consumption and parsing of the Genesys Cloud event stream. I need to ensure no events are lost during the reconnection window. This is critical for our compliance audits. I have checked the Audit API logs and confirmed the events were generated by Genesys Cloud. They just never made it to Kafka. Here is my reconnection logic and event handler:

const ws = new WebSocket(wssUrl, { headers: { Authorization: `Bearer ${token}` } });

ws.on('open', () => {
 console.log('Connected to Genesys Cloud WSS');
 const subscription = {
 channel: 'analytics:notification',
 filter: { eventType: ['user.login', 'user.logout'] }
 };
 ws.send(JSON.stringify(subscription));
});

ws.on('message', (data) => {
 const event = JSON.parse(data);
 console.log('Received event:', event.eventType);
 // Producer.send(event);
});

The first message logs correctly. After a reconnect, the log shows undefined. Why does the eventType field disappear or become inaccessible after the first reconnection? Am I missing a step in the WebSocket subscription refresh process? Do I need to explicitly unsubscribe before resubscribing? Or is this a known behavior with the Notification API stream?

TL;DR: You need to manage the lastEventId in your state store and request it on reconnect.

const sub = platformClient.AnalyticsApi.getAnalyticsNotificationsSubscription({
 body: {
 lastEventId: storedLastEventId, // Critical for gapless resume
 types: ['user.login', 'user.logout'],
 filter: { ... }
 }
});

The server skips events before lastEventId. Without it, you start fresh and miss the gap.

You need to ensure your lastEventId is persisted across process crashes, not just network blips. The suggestion above is correct for the API call, but if your Node process dies, you lose that state. I handle this in my Chrome extension content scripts by writing the ID to chrome.storage.local before the connection drops. For Node, use a lightweight file store or Redis. If you resume without the correct ID, you get duplicates. If you resume with an invalid ID, you get gaps. Here is the pattern:

  1. Store lastEventId immediately on receipt.
  2. On reconnect, read the stored ID.
  3. Pass it in the subscription body.
const fs = require('fs');
const lastId = fs.existsSync('lastId.json') ? JSON.parse(fs.readFileSync('lastId.json')).id : null;

const sub = platformClient.AnalyticsApi.getAnalyticsNotificationsSubscription({
 body: {
 lastEventId: lastId, // Prevents replay or gaps
 types: ['user.login', 'user.logout']
 }
});

sub.events.subscribe(event => {
 const newId = event.id;
 fs.writeFileSync('lastId.json', JSON.stringify({ id: newId })); // Persist immediately
 // forward to Kafka
});

This ensures gapless audit trails even if your server restarts.

Yep, this is a known issue… The documentation states that lastEventId is mandatory for gapless resume, yet the SDK often ignores it if not explicitly passed in the body. Ensure your getAnalyticsNotificationsSubscription call includes the exact ID from your store, otherwise you will miss events during the reconnect window.

This looks like a transport layer failure, not an API logic error. The ws library handles the TCP reconnection, but you are likely missing the application-level ping/pong heartbeat logic required by the Genesys Notification API to keep the channel alive during re-establishment.

Parameter Value
pingInterval 15000 ms
pingTimeout 5000 ms
reconnect true

Ensure your WebSocket constructor explicitly sets these intervals. Without them, the server closes the connection silently, and your reconnect logic never triggers properly.