WebRTC has a special talent for turning five independent systems into one vague complaint:

The call connected, but the audio did not work.

That sentence can describe a failed WebSocket, a rejected SIP registration, an ICE candidate that cannot carry packets, a DTLS problem, one-way RTP, a muted browser element, a denied microphone, or a call that never created the second media leg.

The fastest way through that ambiguity is not a longer list of settings. It is a strict order of questions. Treat WebRTC as a sequence of boundaries, prove each boundary with one piece of evidence, and stop at the first disagreement.

WebRTC is a pipeline, not a checkbox

In a browser-based voice product, “WebRTC” is usually shorthand for a path like this:

EXAMPLE

application state
  -> HTTPS provisioning
  -> WSS transport
  -> SIP registration and dialog
  -> SDP offer / answer
  -> ICE connectivity
  -> DTLS key exchange
  -> SRTP packets
  -> browser jitter buffer and playback

Each stage can succeed while the next one fails. A provisioning endpoint returning 200 does not prove that the browser opened WSS. A successful SIP 200 OK does not prove that the selected ICE candidate pair carries media. An ICE state of connected does not prove that useful RTP exists in both directions. Growing packetsReceived does not prove the user can hear the audio.

This is why one green Connected badge is rarely enough. It compresses several contracts into one product state and removes the clue that operators actually need: which contract failed first?

Draw the path that really exists

Before opening developer tools, draw the deployed call path. Use hostnames, proxies and media hops that exist in production, not the clean architecture from a slide.

EXAMPLE

Browser
  | HTTPS /api/softphone/config
  | WSS   /freeswitch-ws
  v
Reverse proxy
  | WSS
  v
FreeSWITCH
  | SIP + RTP
  v
Carrier

If Kamailio, RTPengine, a TURN service, a load balancer or another SBC sits in the path, add it. Also mark where TLS terminates and which component rewrites SDP.

The drawing immediately produces testable questions:

  • Did the browser receive the expected WSS URL and SIP identity?
  • Did the reverse proxy upgrade the WebSocket and trust its upstream?
  • Did the SIP dialog reach the intended media node?
  • Did SDP advertise addresses reachable from each side?
  • Is media anchored where the architecture expects it to be?

A surprising number of “WebRTC bugs” are simply mismatches between the assumed topology and the deployed one.

Name the failed phase before changing anything

Write the symptom as a small boundary matrix:

EXAMPLE

registration:      succeeds
outbound INVITE:   succeeds
remote answer:     succeeds
browser hears:     nothing
remote hears:      browser
affected phase:    after answer
affected browsers: Chrome and Edge

That description is already more useful than “no audio.” It says signalling is probably not the primary failure, the browser microphone path works, and the defect is directional.

Useful phase boundaries include:

  • before SIP registration;
  • during INVITE or answer;
  • during early media;
  • only after answer;
  • after hold, transfer or renegotiation;
  • only when the browser is backgrounded;
  • only on one network, region or browser family.

The phase often identifies the code path. Early media and answered media may use different bridges. A transfer may move the call to another media server. A mobile browser may suspend capture after a lifecycle transition. If the symptom starts at a specific transition, debug that transition first.

Prove WSS and SIP separately

When the softphone says Unavailable, start below React state.

First inspect the browser Network panel. The WSS request should receive a successful protocol upgrade and remain open. A 502, TLS error or immediate close is a proxy or upstream problem even if provisioning worked perfectly.

Then inspect SIP messages. A healthy transport can still carry a failed registration:

EXAMPLE

WSS open
REGISTER sent
401 challenge received
REGISTER with authorization sent
200 OK received

These are different checkpoints:

  • configuration loaded;
  • WebSocket upgraded;
  • SIP authentication completed;
  • registration remained alive through refreshes;
  • the INVITE used the same reachable transport.

Reverse proxies deserve particular suspicion because they can fail only in production. An internal self-signed WSS certificate, a missing upgrade header, an idle timeout or path rewrite can make the browser look broken while the SIP endpoint itself is healthy.

Read SDP as a routing document

SDP is not decorative payload. It says where media should go, which codecs can be used and how the secure session is established.

For each offer and answer, record:

  • connection address and media port;
  • ICE username fragment and password;
  • host, server-reflexive and relay candidates;
  • DTLS fingerprint and setup role;
  • offered and selected codecs;
  • RTP header extensions and direction attributes;
  • whether a re-INVITE changed any of them.

Then compare SDP with the topology drawing. A private address advertised to a public browser is suspicious. So is an RTPengine deployment that should anchor media but leaves an unreachable endpoint in the final answer.

Do not “fix” SDP by rewriting everything at every proxy. Every component that modifies it becomes an owner of the media route. Keep that ownership explicit.

ICE connected is necessary, not sufficient

ICE answers a narrow question: did the peers nominate a candidate pair that passed connectivity checks?

The selected pair is more informative than the final state. In RTCPeerConnection.getStats(), inspect the nominated candidate pair and its local and remote candidates:

EXAMPLE

local candidate type:   host | srflx | relay
remote candidate type:  host | srflx | relay
transport protocol:     udp | tcp
current RTT
bytes sent / received

If the issue appears only on corporate networks or mobile carriers, candidate type matters. A direct UDP path may work in the office and fail elsewhere. TURN is not a universal requirement for every SIP/WebRTC topology, but when the browser must traverse restrictive networks, a tested relay path is often the difference between a demo and a product.

Also watch the byte counters. An ICE pair can be selected while useful media counters remain flat. Connectivity checks proved the path existed at one moment; they did not prove that the application sent audio through it.

Separate DTLS, SRTP and RTP direction

After ICE, DTLS negotiates the keys used for SRTP. A fingerprint mismatch, role conflict or handshake failure can leave signalling complete and media absent.

The evidence should come from both ends:

  • browser peer-connection events and stats;
  • FreeSWITCH or media-proxy logs;
  • a packet capture near the media server;
  • channel UUIDs and the SIP Call-ID tying them together.

Encrypted WebRTC payloads are still useful in a capture. You can see packet addresses, ports, sizes, sequence timing and whether traffic exists in each direction. If packets reach FreeSWITCH from the browser but nothing leaves toward the carrier, the browser is not the broken boundary. If carrier RTP reaches the media server but no SRTP leaves toward the browser, inspect the bridge, media ownership and server-side pacing.

Always write direction explicitly:

EXAMPLE

browser -> FreeSWITCH   packets increasing
FreeSWITCH -> carrier   packets increasing
carrier -> FreeSWITCH   packets increasing
FreeSWITCH -> browser   no packets

“RTP is flowing” is not a conclusion until it names a leg and a direction.

The browser can receive audio and still play silence

Suppose inbound packetsReceived and bytesReceived keep growing. The network path is real, but the user still hears nothing. The next boundary is inside the browser.

Check:

  • whether the remote track was attached to the intended audio element;
  • whether play() was allowed after a user gesture;
  • whether the element is muted or routed to the wrong output device;
  • whether the track is enabled and live;
  • whether the tab, operating system or Bluetooth device changed output;
  • whether jitter-buffer delay and concealed samples are growing;
  • whether an application rerender replaced the media element.

For outbound audio, separate permission from capture. A browser may have microphone permission while the selected device is missing, silent or replaced. Inspect the applied track settings, audio level and outbound RTP counters. If the media-source level moves but bytesSent does not, the failure lies after capture. If both move but the remote side receives nothing, continue along the path.

This distinction matters to product teams because retrying the SIP call will not repair a detached HTML audio element.

Build one evidence bundle per call

The minimum useful debugging bundle is small enough to collect routinely:

EXAMPLE

application call ID
SIP Call-ID
agent and customer channel UUIDs
peer connection identifier
timestamps in one timezone
offer, answer and any renegotiation SDP
selected ICE candidate pair
inbound and outbound getStats summary
SIP trace
media-server log slice
short packet capture when permitted

Correlation is the important part. A perfect packet capture from the wrong concurrent call is worse than no capture because it creates a confident false conclusion.

For production systems, make the identifiers travel with the call. A custom SIP header can connect an application call ID to the browser dialog and FreeSWITCH channel. Structured logs can then include the same handle. Browser stats can be stored as bounded summaries at meaningful moments rather than as an endless raw stream.

A debugging order that scales beyond one engineer

I use this sequence:

  1. Describe direction, phase, affected environments and repeatability.
  2. Draw the deployed signalling and media path.
  3. Verify provisioning, WSS and SIP registration as separate steps.
  4. Correlate the application call, SIP dialog, media channels and browser peer connection.
  5. Compare offer, answer and selected ICE candidate pair with the intended topology.
  6. Prove packet movement in every direction around the media server.
  7. Inspect browser capture, jitter-buffer and playback evidence.
  8. Change one boundary and repeat the same scenario.

This order is intentionally boring. It prevents a team from changing codecs, NAT rules, browser constraints and FreeSWITCH timers in the same deploy and then declaring victory because one test call sounded better.

Reliability starts with debuggability

For the business, WebRTC debugging is not an obscure browser concern. It affects support time, call completion, agent trust and the speed at which engineering can safely ship changes.

The architecture is easier to operate when it exposes meaningful stages:

EXAMPLE

CONFIGURED -> WSS_CONNECTED -> REGISTERED -> INVITING
-> ICE_CONNECTED -> MEDIA_FLOWING -> ENDED

Those do not need to become a wall of technical labels in the customer interface. Internally, however, they should exist as timestamps, metrics and searchable evidence.

The goal is not to eliminate every WebRTC failure. Browsers, networks and devices will always introduce variation. The goal is to make “the call connected but audio did not work” the beginning of a short investigation, not the beginning of a settings lottery.