Full JsSIP.js WebRTC SIP Tutorial with Example

Published On: November 10, 2025
Follow Us
Complete JsSIP.js Tutorial for Beginners

1️⃣ Prerequisites

  1. Asterisk (16+) installed with PJSIP.
  2. Modern web browser (Chrome, Firefox) with microphone access.
  3. Local network setup (no STUN/ICE needed for LAN testing).
  4. Microphone and speakers connected.

2️⃣ Asterisk Configuration

2.1 Enable HTTP & WebSocket

/etc/asterisk/http.conf:

[general]
enabled=yes
bindaddr=0.0.0.0
bindport=8088

Reload:

asterisk -rx "module reload res_http_websocket.so"

2.2 Configure PJSIP Transport

/etc/asterisk/pjsip.conf:

[transport-ws]
type=transport
protocol=ws
bind=0.0.0.0
local_net=192.168.0.0/24  ; your LAN subnet

2.3 Create a WebRTC Endpoint

[webrtc-client]
type=aor
max_contacts=1

[webrtc-client]
type=auth
auth_type=userpass
username=webrtc-client
password=webrtcpass

[webrtc-client]
type=endpoint
aors=webrtc-client
auth=webrtc-client
context=from-internal
disallow=all
allow=opus,ulaw,alaw
direct_media=no
media_encryption=no
ice_support=no

2.4 Configure Dialplan

/etc/asterisk/extensions.conf:

[from-internal]
exten => 1000,1,Answer()
 same => n,Playback(hello-world)
 same => n,Hangup()

Reload:

asterisk -rx "pjsip reload"
asterisk -rx "dialplan reload"

3️⃣ Web Client Code (HTML + JsSIP.js)

Save as index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JsSIP WebRTC SIP Agent</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
button { margin: 5px; padding: 10px; }
#ua-status { font-weight: bold; }
</style>
</head>
<body>

<h2>WebRTC SIP Agent</h2>
<div>Status: <span id="ua-status">Disconnected</span></div>
<button id="callBtn">Call 1000</button>
<button id="hangupBtn" disabled>Hang Up</button>
<h3>Remote Audio</h3>
<audio id="remoteAudio" autoplay></audio>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jssip/3.11.0/jssip.min.js"></script>
<script>
/* --- 1. Configure JsSIP User Agent --- */
const socket = new JsSIP.WebSocketInterface('ws://192.168.0.10:8088/ws'); 
const ua = new JsSIP.UA({
  sockets: [socket],
  uri: 'sip:webrtc-client@192.168.0.10',
  password: 'webrtcpass',
  session_timers: false
});

/* --- 2. UA Event Listeners --- */
ua.on('connected', () => document.getElementById('ua-status').textContent = 'Connected');
ua.on('disconnected', () => document.getElementById('ua-status').textContent = 'Disconnected');
ua.on('registered', () => console.log('Registered with SIP server'));
ua.on('registrationFailed', (e) => console.log('Registration failed:', e.cause));

ua.start();

/* --- 3. Handle Outgoing Call --- */
let session = null;

document.getElementById('callBtn').onclick = async () => {
  try { await navigator.mediaDevices.getUserMedia({ audio: true }); }
  catch { alert('Microphone access denied!'); return; }

  session = ua.call('sip:1000@192.168.0.10', {
    mediaConstraints: { audio: true, video: false },
    rtcOfferConstraints: { offerToReceiveAudio: 1 }
  });

  document.getElementById('hangupBtn').disabled = false;

  session.connection.addEventListener('track', (e) => {
    document.getElementById('remoteAudio').srcObject = e.streams[0];
  });

  session.on('ended', () => { session = null; document.getElementById('hangupBtn').disabled = true; });
  session.on('failed', () => { session = null; document.getElementById('hangupBtn').disabled = true; });
};

/* --- 4. Handle Hang Up --- */
document.getElementById('hangupBtn').onclick = () => {
  if(session) session.terminate();
  session = null;
  document.getElementById('hangupBtn').disabled = true;
};

/* --- 5. Handle Incoming Calls --- */
ua.on('newRTCSession', (e) => {
  const s = e.session;
  if(e.originator === 'remote') {
    session = s;
    s.answer({ mediaConstraints: { audio: true, video: false } });
    s.connection.addEventListener('track', (ev) => {
      document.getElementById('remoteAudio').srcObject = ev.streams[0];
    });
    s.on('ended', () => session = null);
    s.on('failed', () => session = null);
  }
});
</script>

</body>
</html>

4️⃣ Testing

  1. Open index.html in Chrome/Firefox.
  2. Allow microphone access.
  3. Click Call 1000 → you should hear the Asterisk playback.
  4. Incoming calls to the WebRTC client will auto-answer and play audio.

5️⃣ Next Steps / Enhancements

  1. Add video calls: set video: true in mediaConstraints.
  2. Show call status: ringing, connected, ended.
  3. Handle multiple simultaneous calls with multiple RTCSession objects.
  4. Implement mute/unmute, hold, and transfer features.
  5. Learn SIP signaling basics: INVITE, ACK, BYE, REGISTER.

6️⃣ Troubleshooting Tips

IssueFix
One-way audioEnsure direct_media=no and media_encryption=no in Asterisk.
Call failsVerify WebSocket connection: telnet 192.168.0.10 8088.
Browser blocks microphoneAllow access in browser settings.
Audio not playingCheck remoteAudio.srcObject is correctly set.

✅ This is a fully functional example of a JsSIP WebRTC SIP client for Asterisk

1️⃣ HTML + CSS + JsSIP.js

Save this as index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JsSIP WebRTC Full Client</title>
<style>
body { font-family: Arial; padding: 20px; }
button { margin: 5px; padding: 10px; }
#ua-status { font-weight: bold; }
#calls { margin-top: 20px; }
.call { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
video, audio { width: 300px; height: 200px; background: #000; }
</style>
</head>
<body>

<h2>JsSIP WebRTC SIP Client</h2>
<div>Status: <span id="ua-status">Disconnected</span></div>

<input type="text" id="dest" placeholder="Enter SIP URI e.g. 1000">
<button id="callBtn">Call</button>
<button id="hangupAllBtn">Hang Up All</button>
<button id="muteBtn">Mute</button>

<h3>Active Calls</h3>
<div id="calls"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jssip/3.11.0/jssip.min.js"></script>
<script>
/* --- 1. Configure JsSIP UA --- */
const socket = new JsSIP.WebSocketInterface('ws://192.168.0.10:8088/ws'); 
const ua = new JsSIP.UA({
  sockets: [socket],
  uri: 'sip:webrtc-client@192.168.0.10',
  password: 'webrtcpass',
  session_timers: false
});

ua.on('connected', () => document.getElementById('ua-status').textContent = 'Connected');
ua.on('disconnected', () => document.getElementById('ua-status').textContent = 'Disconnected');
ua.on('registered', () => console.log('Registered'));
ua.on('registrationFailed', e => console.log('Registration failed', e.cause));

ua.start();

/* --- 2. Call Management --- */
let sessions = [];
let localStream = null;
let isMuted = false;

/* Get microphone & camera */
async function initMedia() {
  try {
    localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
  } catch (e) {
    alert('Cannot access microphone/camera: ' + e);
  }
}

/* --- 3. Outgoing Call --- */
document.getElementById('callBtn').onclick = async () => {
  const target = document.getElementById('dest').value;
  if(!target) { alert('Enter SIP URI'); return; }
  await initMedia();

  const session = ua.call(target, {
    mediaConstraints: { audio: true, video: true },
    pcConfig: { iceServers: [] }, // no STUN/ICE for LAN
    rtcOfferConstraints: { offerToReceiveAudio: 1, offerToReceiveVideo: 1 }
  });

  addSession(session);
};

/* --- 4. Manage Sessions --- */
function addSession(session) {
  sessions.push(session);

  const callDiv = document.createElement('div');
  callDiv.className = 'call';
  callDiv.innerHTML = `<strong>Call with ${session.remote_identity.uri.user}</strong>
                       <div>Status: <span class="status">Connecting</span></div>
                       <video autoplay playsinline></video>
                       <audio autoplay></audio>`;
  document.getElementById('calls').appendChild(callDiv);

  const statusSpan = callDiv.querySelector('.status');
  const videoEl = callDiv.querySelector('video');
  const audioEl = callDiv.querySelector('audio');

  session.on('progress', () => statusSpan.textContent = 'Ringing');
  session.on('accepted', () => statusSpan.textContent = 'Connected');
  session.on('ended', () => { statusSpan.textContent = 'Ended'; removeSession(session, callDiv); });
  session.on('failed', () => { statusSpan.textContent = 'Failed'; removeSession(session, callDiv); });

  session.connection.addEventListener('track', e => {
    if(e.track.kind === 'video') videoEl.srcObject = e.streams[0];
    if(e.track.kind === 'audio') audioEl.srcObject = e.streams[0];
  });

  // Attach local tracks
  localStream.getTracks().forEach(track => session.connection.addTrack(track, localStream));
}

/* --- 5. Hang Up All Calls --- */
document.getElementById('hangupAllBtn').onclick = () => {
  sessions.forEach(s => s.terminate());
  sessions = [];
  document.getElementById('calls').innerHTML = '';
};

/* --- 6. Mute / Unmute --- */
document.getElementById('muteBtn').onclick = () => {
  if(!localStream) return;
  isMuted = !isMuted;
  localStream.getAudioTracks().forEach(track => track.enabled = !isMuted);
  document.getElementById('muteBtn').textContent = isMuted ? 'Unmute' : 'Mute';
};

/* --- 7. Handle Incoming Calls --- */
ua.on('newRTCSession', async e => {
  const session = e.session;
  if(e.originator === 'remote') {
    await initMedia();
    session.answer({ mediaConstraints: { audio: true, video: true } });
    addSession(session);
  }
});

/* --- 8. Optional: Handle Multiple Calls --- */
// sessions array already handles multiple active calls

</script>
</body>
</html>

Features Included

  1. Audio & Video calls (set video: true in mediaConstraints).
  2. Multiple call handling using an array of sessions.
  3. Call status display – connecting, ringing, connected, ended, failed.
  4. Mute/Unmute button.
  5. Hang Up All Calls button.
  6. Incoming call auto-answer with video/audio.
  7. Local & remote media streams properly attached.

Testing

  1. Open in a browser (Chrome or Firefox).
  2. Allow microphone and camera.
  3. Enter a SIP URI (e.g., sip:1000@192.168.0.10) and click Call.
  4. Multiple calls can be made simultaneously.
  5. Incoming calls will auto-answer with video/audio.
  6. Use Mute and Hang Up All buttons for control.

This is a complete WebRTC SIP client for LAN environments using JsSIP.js + Asterisk, and it’s ready for extension:

  • Add call transfer
  • Add hold/resume
  • Add call recording
  • Enhance UI/UX with call queues

sapan singh

👨‍💻 About Sapan Singh Hi, I’m Sapan Singh — a passionate software developer with a strong love for technology, gaming, and building useful digital tools.

Join WhatsApp

Join Now

Join Telegram

Join Now

Leave a Comment