Using ExpressTURN with JsSIP, Asterisk & FreeSWITCH

TURN config goes in the browser, not the PBX.

Where TURN belongs in a SIP-WebRTC stack

Your PBX (Asterisk, FreeSWITCH, Kamailio, OpenSIPS) speaks SIP and RTP. The browser-based softphone speaks WebRTC. The TURN server only relays the WebRTC media leg — between the browser and whatever endpoint terminates the WebRTC. The PBX itself does not connect to TURN; it sees standard RTP from a public-facing WebRTC bridge.

JsSIP example

// npm install jssip
import JsSIP from 'jssip';

const ua = new JsSIP.UA({
  uri: 'sip:alice@your-pbx.example.com',
  password: 'secret',
  sockets: [new JsSIP.WebSocketInterface('wss://your-pbx.example.com:7443')],
  pcConfig: {
    iceServers: [
      { urls: 'stun:stun.expressturn.com:3478' },
      {
        urls: [
          'turn:relay1.expressturn.com:3478?transport=udp',
          'turn:relay1.expressturn.com:3478?transport=tcp',
          'turns:relay1.expressturn.com:443?transport=tcp'
        ],
        username: 'YOUR_EXPRESSTURN_USERNAME',
        credential: 'YOUR_EXPRESSTURN_PASSWORD'
      }
    ],
    iceTransportPolicy: 'all'
  }
});
ua.start();
ua.call('sip:bob@your-pbx.example.com');

SIP.js example

import { UserAgent } from 'sip.js';

const userAgent = new UserAgent({
  uri: UserAgent.makeURI('sip:alice@your-pbx.example.com'),
  authorizationPassword: 'secret',
  transportOptions: { server: 'wss://your-pbx.example.com:7443' },
  sessionDescriptionHandlerFactoryOptions: {
    peerConnectionConfiguration: {
      iceServers: [
        { urls: 'stun:stun.expressturn.com:3478' },
        {
          urls: ['turn:relay1.expressturn.com:3478?transport=udp',
                 'turns:relay1.expressturn.com:443?transport=tcp'],
          username: 'YOUR_EXPRESSTURN_USERNAME',
          credential: 'YOUR_EXPRESSTURN_PASSWORD'
        }
      ]
    }
  }
});

Asterisk side notes

In pjsip.conf set the WebRTC transport on port 8089/wss with appropriate media_address and external_media_address if Asterisk is behind NAT. Asterisk uses STUN for its own NAT traversal — you don't need to configure TURN on Asterisk because it has a public address. The browser side handles TURN for itself.

FreeSWITCH side notes

For mod_verto WebRTC clients, configure verto.conf.xml with appropriate WebSocket and external IP. As with Asterisk, FreeSWITCH itself doesn't connect through TURN; the browser does.

Kamailio / OpenSIPS WebRTC

Both proxies handle SIP signaling. WebRTC media bridging usually goes through a separate RTP engine (rtpengine for Kamailio, mediaproxy or rtpproxy for OpenSIPS). The bridge has a public IP and doesn't need TURN; the browser does.

Common pitfalls

  • One-way audio: Almost always a NAT/ICE issue. Force-relay testing (iceTransportPolicy: 'relay') reveals whether TURN is the problem or the bridge is.
  • Cert mismatch on WSS: The WebRTC handshake requires DTLS; if your WSS cert is wrong, the SIP signaling works but media never establishes.
  • Codec mismatch: Asterisk and the browser must agree on a codec (Opus is the safest choice for browsers).

Related: TURN for VoIP & SIP · PeerJS recipe

Done.

Get Free TURN Credentials