How does the WebSocket Notification API handle STUN negotiation when the Express middleware drops the JWT validation? GC version sits at 2024-3.412.0. The Architect flow pushes a webrtc.session.started event, but the Node handler doesn’t validate the token properly. It’s returning a 403 Forbidden on the /api/v2/authorization/token endpoint. Console logs show the softphone client just hanging after twelve seconds. Doing jack all with the TURN relay config right now.
app.post('/gc/webrtc/token', async (req, res) => {
// introspection fails here
})
Payload shows a mismatched grant_type.
The docs state: “The WebSocket Notification API does not handle STUN negotiation.” That’s a client-side signaling issue, not an auth middleware problem. Your 403 is likely a bad scope or expired token, not the STUN relay.
The WebSocket Notification API documentation explicitly states it handles subscription management and event delivery, not media signaling. Mixing auth logic with media negotiation in Express middleware creates a fragile architecture. The 403 you’re seeing on /api/v2/authorization/token is likely because your middleware is intercepting a request that doesn’t need interception, or the token being passed lacks the webphone scope.
When instrumenting this with New Relic, you’ll see the latency spike before the media path even initializes. Here’s how to isolate the token validation from the WebRTC handshake. You should validate the JWT in a separate route or middleware layer that doesn’t interfere with the WebSocket upgrade request.
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();
// Do NOT validate tokens on the WebSocket upgrade path
// Validate on API calls that trigger the session
router.post('/start-session', (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).send('No token');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Log custom event to New Relic for tracking
newrelic.recordCustomEvent('GC_Session_Start', {
userId: decoded.sub,
timestamp: Date.now()
});
res.json({ status: 'ok' });
} catch (err) {
res.status(403).send('Invalid token');
}
});
// WebSocket setup should bypass JWT validation if using token in URL params
// or handle it via a separate secure channel
module.exports = router;
The risk here is blocking the WebSocket upgrade. If you enforce JWT validation on the HTTP upgrade request, the handshake fails before the STUN/TURN logic even runs. Keep the auth layer separate. The client should fetch the token first, then initiate the WebSocket connection with that token in the query parameters. This keeps the signaling path clean. New Relic traces will show distinct spans for auth and media setup. Mixing them obscures the root cause. Check your scope configuration in the OAuth client settings. Missing webphone or webphone:write will cause this exact 403.