Troubleshooting & FAQ
Troubleshooting & FAQ
Common failure modes and how to diagnose them. For end-to-end patterns, see the guides; for full API contracts, see the reference.
Connection issues
InvalidCredentialsError on connect
Cause: Token is expired, malformed, or minted for a different SignalWire space.
Fix:
- Mint a fresh SAT from your backend — see Authentication.
- Confirm the token was issued by the space you’re connecting to.
- Check that the full token string was copied (SATs are long; truncation is common).
Connection fails without an obvious error
Errors that happen outside of an await flow surface on
errors$. If you
never subscribe to it, those errors are silently dropped — subscribe
during client construction so they always reach your logs.
NotConnectedError when calling dial()
Cause: Calling dial() before the client finished connecting.
Wait for ready$ to emit true:
WebSocket disconnects frequently
Causes: Unstable network, corporate firewall/proxy blocking WebSocket, idle timeout.
The SDK reconnects automatically. Drive a “reconnecting” banner off
isConnected$:
Video / Audio issues
Video is black
Causes: Camera permissions denied, camera in use by another app, wrong camera selected, hardware issue.
No remote audio
Causes: Output device not selected, remote participant muted, browser blocking unmuted autoplay, or the muted attribute left on the <video> element.
Browsers block autoplay with audio until the page has been interacted
with — play() will reject. Catch that promise and surface a
play-to-start button if it fails.
Remote can’t hear me
Echo or feedback
Use headphones, or enable echo cancellation:
Permission denied for camera/microphone
HTTPS is required. getUserMedia only works on secure contexts (HTTPS or localhost).
Call issues
Call stays in trying state
Causes: Invalid destination, destination not reachable, network/firewall blocking.
Call connects but no media flows
Cause: ICE connection failed (firewall blocking UDP) or TURN server unreachable.
Can’t receive inbound calls
Causes: register()
was never called, or the token can’t register. Only full Subscriber
Access Tokens can register; Guest SATs, Invite SATs, and embed tokens
are outbound-only.
Inbound calls then surface on session.incomingCalls$.
DTMF tones not working
Send digits only after the call is connected:
UI issues
Video element shows nothing
UI doesn’t update when state changes
Subscribe immediately after getting the object — BehaviorSubjects emit current state on subscribe.
Memory leak / page slows down
Almost always a missing unsubscribe on a long-lived subscription. See RxJS Primer → Cleanup and the framework patterns in Framework Integration.
Browser-specific issues
Safari: video doesn’t play
Safari has strict autoplay policies. Add playsinline and handle the play promise:
Firefox: no audio output selection
Firefox doesn’t fully support setSinkId. Audio plays through default output.
Mobile: camera switches unexpectedly
Device rotation or app switching can reset the camera.
Debugging
Verbose logging
The SDK emits debug-level logs to the browser console. Filter by
signalwire in DevTools to isolate them.
Inspect WebSocket traffic
DevTools → Network → “WS” filter → click the connection → Messages tab.
Get call statistics
call.rtcPeerConnection
is the underlying RTCPeerConnection — it’s undefined until media
negotiation starts, so guard before reading it.
Test without real media
Chrome flag: --use-fake-device-for-media-stream. Or generate a canvas stream:
FAQ
Do I need HTTPS?
Yes for production. WebRTC’s getUserMedia requires a secure context. Localhost is exempt for development.
What browsers are supported?
Modern Chrome, Firefox, Safari, and Edge. No IE11.
Can I use this in Node.js?
No — the SDK is browser-only. Use the SignalWire REST APIs or server-side SDKs.
How do I implement a mute button?
call.self is null until the local participant joins — always check.
How do I get call duration?
Can I record calls?
Recording is controlled by the SignalWire platform, not the browser.
startRecording()
is on the API surface but not yet implemented in v4 and will throw
when called. Recording state configured server-side still surfaces
through recording$.
Why “Unimplemented” errors?
Some methods are on the API surface but pending implementation, and
others depend on capabilities the server hasn’t granted to this call.
Check capabilities$
to gate the UI on what’s actually available.