WebRTC softphone media handshake failing with SDP negotiation error on hybrid CXone route

I’m hitting a wall with the custom softphone UI whenever routing targets a CXone-hosted queue. Pure Genesys Cloud destinations work without issue. We’re running JS SDK 3.24.3 inside a React frontend. The WebSocket connection holds steady, signaling completes, but the browser console immediately dumps the SDP error right after createConnection resolves.

Network trace confirms the initial ICE candidates exchange, then the server pushes back a 480 Busy Here with a malformed SDP answer. I verified the STUN servers listed in the ORG CONFIG. They respond fine from Australia/Sydney. I attempted forcing plan-b SDP semantics in the SDK CONFIG since the CXone side appears to be sending unified-plan offers. It’s doing jack all.

Here is the setup and what has been tested:

  • Genesys Cloud Org: EU10
  • CXone Org: US1 (hybrid routing via SIP Interop connector)
  • JS SDK: 3.24.3 (loaded via CDN)
  • Browser: Chrome 120, Edge 119
  • SDK CONFIG overrides: stunServers, turnServers, sdpSemantics: "plan-b", audioCodecs: ["opus", "g722", "pcmu"]
  • Verified SIP TRUNK CODECS match between platforms
  • Ran Wireshark on the media stream, seeing repeated STUN binding requests timing out after 15s
  • Swapped to a fresh SDK instance, cleared browser cache, disabled all extensions

The error payload from the signaling channel looks like this:

{
 "code": "sdp_negotiation_failed",
 "message": "Remote SDP answer contains unsupported codec payload type 98",
 "details": "ICE state: failed"
}

Payload type 98 maps to G.722 in the offer, but the answer strips it out. Ran Wireshark again just to be sure. The SDK isn’t falling back to PCMU like it should. The ARCHITECT FLOW for the hybrid route has Enable WebRTC toggled on. Destination points straight to cxone_queue_id. Logs on the CXone side show the call arriving but immediately rejecting the media parameters. Mic stays hot on the Genesys side, audio never bridges.

The fallback logic just won’t trigger on this build. What specific API INTEGRATION parameter or ROUTING SETTING am I missing to force proper codec negotiation across the hybrid connector? I’ve exhausted the standard SDK CONFIG overrides and need a definitive path forward.

From a Salesforce admin perspective, when SDP negotiation breaks on hybrid routes, it’s almost always a media path mismatch rather than a pure signaling failure. As we’ve discussed in several recent community threads, once Genesys Cloud hands off to a CXone-hosted queue, the underlying media server swaps out mid-session. It’s a pretty common gotcha. If you’re running custom React softphones on older SDK versions, they tend to cache that initial SDP format and outright reject the answer because the codec negotiation shifts based on the CXone media region.

Your first step should be to validate the CTI adapter configuration. The Genesys Cloud for Salesforce managed package (v8.4+) actually ships with a built-in fallback for hybrid routing that forces the softphone to renegotiate codecs automatically. If you’re spinning up a custom UI alongside standard click-to-dial and screen pop flows, you’ll want to hook into the state change event and reset the peer connection before the 480 error fires. I’d strongly advise against relying on the default ICE timeout here. You can handle it like this:

softphone.on('mediaStateChange', (state) => {
 if (state === 'connecting' && queueType === 'hybrid') {
  pc.setLocalDescription(null);
  pc.restartIce();
 }
});

On the Salesforce side, call logging will still function correctly even if the softphone connection drops. To keep your agent workflow intact while the media handshake sorts itself out, route the hybrid queue through a Data Action that pushes the callId and target to a custom object. You can then trigger a screen pop via the CTI adapter. This mirrors how we handle Apex-driven call logging hooks, ensuring the agent still gets the context they need without breaking the UI.

Make sure the hybrid route in Genesys isn’t set to earlyMedia. That flag breaks custom WebRTC implementations pretty hard, especially when you’re trying to maintain clean screen pop triggers. Toggle it off in the routing script. Also, verify that your SFDC org isn’t intercepting the WebSocket with a custom security policy. I’ve seen cases where session replay or compliance recorders mess with the ICE candidates, which throws off the CTI adapter’s ability to maintain the session.

Finally, bump the SDK to 3.25.1. The patch fixes a known SDP answer parser bug that trips up on CXone media servers. It’s a straightforward update that aligns with the current GC for Salesforce package recommendations.

The media server swap on hybrid routes definitely breaks the cached SDP answer. The JS SDK createConnection flow locks onto the initial ICE topology, then chokes when CXone pushes the renegotiation payload. Check the -webrtc source around session.js line 412. The parser drops the a=mid: attribute if the sdpMediaDirection flag isn’t explicitly set to recvonly during the hybrid handoff.

You’ll need to force a cleanup before the second handshake. Try clearing the local description cache manually:

connection.setLocalDescription(null);
connection.restartIce();

Does your React wrapper handle the ontrack event before or after the SDP answer arrives? The pipeline stalls if the browser tries to attach the stream too early. A temporary fix is to inject a 200ms delay on the createConnection mise resolve, though it’s messy. Log a bug on the Genesys GitHub repo if the sdpMediaDirection override doesn’t stabilize the handshake. The maintainers are already patching the SDP parser for cross-platform consistency. Tracking similar drops in v3.25.

1 Like

PlatformClientV2 drops the sdpMediaDirection update when the hybrid route triggers a media server swap, which is why your session.js parser chokes on the missing a=mid: attribute. The JS SDK 3.24.3 client caches the initial ICE topology anyway. That EventBridge webhook payload never reaches the browser with the correct recvonly flag because the backend Lambda handler isn’t stripping the stale SDP lines before forwarding. You’ll need to intercept the notification via the platform’s event subscription mechanism, map the conversation:media:change event, and explicitly patch the answer object before it hits the React frontend. The SDK library name PlatformClientV2 doesn’t give you a clean forceRenegotiate hook, so you have to manually mutate the local session configuration through the exposed WebRtcSession totype. Here’s the webhook config that forces the flag update without dropping the WebSocket:

{
 "name": "HybridSDP-Patch",
 "eventTypes": ["conversation:media:change"],
 "url": "https://your-lambda-endpoint.execute-api.ap-northeast-1.amazonaws.com/d/sdp-fix",
 "headers": {
 "Authorization": "Bearer {{token}}"
 },
 "payload": {
 "mediaDirection": "{{mediaDirection}}",
 "sdpAnswer": "{{sdpAnswer}}",
 "forceRecvOnly": true
 }
}
1 Like

Configuration appears valid. Does your Architect Flow target a Queue or a Direct Routing block?

const sessionConfig = {
 sdpMediaDirection: 'recvonly',
 forceRenegotiation: true
};

*It’s mandatory to preserve the Routing Settings and SDP Direction, or the hybrid media swap will terminate the session.

1 Like