How to Build Video Chat with Xirsys, WebRTC, and PubNub

8 min readMay 23, 2019

In our previous tutorial, we showed you how to integrate WebRTC video calling into your chat app with PubNub’s APIs. PubNub makes this easy, because of the open source, community supported WebRTC JS Package for integrating RTC in browser applications. PubNub provides an instant connection for all internet-capable devices, no matter what network they are on.

In this tutorial, we’ll make our WebRTC-powered video chat more reliable with a service called Xirsys (more on them in a bit). Why Xirsys?

A peer to peer connection with WebRTC isn’t always reliable. The streaming of peer to peer audio and video is not allowed on some networks for security reasons, and sometimes, they just fail. There is a safe, reliable workaround for these scenarios, so your users will always see their friend, regardless of LAN.

Video call with WebRTC on mobile

TURN Servers

WebRTC peer to peer connections fail frequently. They are also often restricted by security. TURN servers allow each WebRTC client to stream all of their audio and video through a 3rd party, so no matter what the security rules are for clients, they can always video conference. As a developer, you can build or buy.

WebRTC Relayed TURN Diagram

Establishing your own global TURN service with multiple points of presence is costly and risky. Xirsys provides on-demand WebRTC infrastructure with their globally distributed TURN server offerings.

This tutorial will expand on the WebRTC app from part 1, with a demonstration for adding Xirsys to make your video connections more reliable.

What do we need to do to accomplish this?

  1. Create a free PubNub account (on this page, below this text) if you have not already.
  2. Set up your app’s web front end (if you don’t have one yet, you can use mine, hosted on GitHub: WebRTC with PubNub Demo app).
  3. Make a free Xirsys account.
  4. Make a Function endpoint with a secure, serverless, Xirsys token service.

If you have not already created a PubNub account, sign up, sign in, and click the deploy button below

In my example PubNub-WebRTC chat application, there is JavaScript code that requests temporary Xirsys tokens from my Xirsys account. When you set up your own WebRTC application, you need to securely provide TURN access tokens to users using the Xirsys API.

A microservice that provides an elegant solution is needed. Functions makes this service extremely easy to deploy, monitor, and auto-scale. If you’re unfamiliar with Functions, read my quick crash-course in Functions.

Here is the architecture of the token service:

Diagram for Xirsys token service with Functions

WebRTC TURN Service with Xirsys

First, we will make the serverless Xirsys token service using Functions. Go to your PubNub Admin Dashboard and click on your app. Next, click on the Functions tab on the left side.


Next, make a new Function.
Click create and make a new API endpoint for your new service. Make sure that it is of type On Request.

Create a new Function

Click on your new endpoint so we can add our JavaScript code.

Once you see the Functions editor, you can add your Xirsys secret information. Next, go to the Xirsys admin panel and grab your Identity, Credential, and Channel for your account. Click on the API Info button to see your app’s credentials.

The Xirsys Admin Panel

Go back to your Functions editor and click the My Secrets button on the left side of the editor. Create a new key and value by entering text in the My Secrets menu input. To make the new service work, add a key called xirsys. Set the xirsys key’s value to your_ident:your_secret from the Xirsys admin panel.

Functions Vault for Secret Keys

Serverless Microservice

Now we add the service code. Copy and paste the code from my Xirsys Token Service GitHub Gist:

const pubnub = require('pubnub');
const kvstore = require('kvstore');
const xhr = require('xhr');
const vault = require('vault');
const base64Codec = require('codec/base64');
// If you are still developing, set the permittedOrigin to * and remove the check on line 15
const permittedOrigin = '';
const xirsysChannel = 'your-xirsys-channel-name';
export default (request, response) => {
    response.headers['Access-Control-Allow-Origin'] = permittedOrigin;
    response.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept';
    if (request.headers.origin !== permittedOrigin) {
        response.status = 401;
        return response.send();
    if (request.method.toUpperCase() === 'GET') {
        const uuid = newUuid() + newUuid() + newUuid() + newUuid();
        const tok = base64Codec.encodeString(uuid);
        return kvstore.set(tok, true, 60).then(() => {
            response.status = 200;
            return response.send(tok);
    } else if (request.method.toUpperCase() === 'PUT') {
        if (!request.headers.tok) {
            response.status = 401;
            return response.send();
        return kvstore.get(request.headers.tok).then((validTok) => {
            if (!validTok) {
                console.error('invlaidtok', request.headers.tok);
                response.status = 401;
                return response.send();
            return vault.get('xirsys').then((xirsysCredential) => {
                const xirsysEndpoint = `https://${xirsysCredential}${xirsysChannel}`;
                return xhr.fetch(xirsysEndpoint, { 'method': 'PUT' })
                .then((res) => {
                    let rtcObj = flatten(JSON.parse(res.body).v);
                    response.status = 200;
                    return response.send(rtcObj);
                }).catch((err) => {
                    response.status = 400;
                    return response.send();
    } else {
        response.status = 401;
        return response.send();
function newUuid() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
  return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
function flatten(rtc) {
    let newcred = { urls: [] };
    rtc.iceServers.forEach((obj) => {
        newcred.username = obj.username;
        newcred.credential = obj.credential;
    return newcred;

At the top of the code file, change the xirsysChannel constant to your channel from the Xirsys admin panel. Also, change the permittedOrigin to your front end application’s URL. If you are still developing, you can set it to * which means any origin, and also delete the “if” that checks the origin “request.headers.origin !== permittedOrigin”.

Bring this back later when you have a permanent origin, for security reasons. If you want to add a monitoring service, check out this Functions monitoring guide.

To globally deploy your new service, click the Start button in the top right corner. Clicking this button will not harm the state of your existing app; it will start the new service.

The Front End Web Application Code

Now that we have created the service for Xirsys, we need to edit the front end JavaScript of the web application. The following code is from the app.js file in the open source WebRTC example app.

let turnToken;
request(turnApiUrl, 'GET').then((response) => { turnToken = response });


Replace the turnApiUrl in your front end JavaScript to the URL for your new service. You can get this URL from the Functions editor. Click on the tab for your new service, and click the Copy URL button on the left side. It will copy the URL to your clipboard.

Paste the URL in your front-end JavaScript code for turnApiUrl. Next, you need to change the procedure for initializing a call in the app around like 163 of app.js.

confirmCall(name).then((yesDoCall) => {
    if (yesDoCall) {
        webRtcPhone.callUser(userToCall, {
            myStream: myAudioVideoStream

Replace the above code with the following code:

confirmCall(name).then((yesDoCall) => {
    if (yesDoCall) {
        // Get new TURN server credentials from Xirsys
        // Not Required but it is more reliable than peer-to-peer
        request(turnApiUrl, 'PUT', {
            'headers': { 'tok': turnToken }
        }).then((response) => {
            rtcConfig.iceServers = [response];
            webRtcPhone.callUser(userToCall, {
                myStream: myAudioVideoStream

Doing all of this adds Xirsys servers to your WebRTC connection automatically, like this example front-end JS code for the WebRTC API. This is what you would need to do if you were not using the WebRTC package (credentials are fake):

// Documentation on this here:
myPeerConnection = new RTCPeerConnection({
        iceServers: [     // Information about ICE servers - Use your own!
                "urls": [
                "username": "c8b127fc-cc35-13e8-999a-123456789abc",
                "credential": "c8b12892-aa35-13e8-9274-123456789abc"

If the app cannot connect users with peer to peer connections, the browser knows to instantly fallback to TURN with Xirsys.



The WebRTC JS Package referenced in this post is open source and community supported.

Use at your own risk!


The WebRTC JS Package has a configuration option (ignoreNonTurn) to ignore all peer to peer connections if you strictly want to use TURN. This makes running your application more costly because all users will stream their audio and video data through Xirsys, instead of only the ones who need to.

// add the WebRTC plugin
let config = {
    ignoreNonTurn: true, // Set this to false if you want to fall back to TURN when peer to peer fails
    myStream: localStream,

Deploy your front-end web app to a provider like GitHub pages and try it out!

Frequently Asked Questions (FAQ) about the WebRTC Package

Is the package officially a part of PubNub?

No. It is an open source project that is community supported. If you have questions or need help, reach out to If you want to report a bug, do so on the GitHub Issues page.

Does PubNub stream audio or video data?

No. PubNub pairs very well with WebRTC as a signaling service. This means that PubNub signals events from client to client using Pub/Sub messaging. These events include:

  • I, User A, would like to call you, User B
  • User A is currently trying to call you, User B
  • I, User B, accept your call User A
  • I, User B, reject your call User A
  • I, User B, would like to end our call User A
  • I, User A, would like to end our call User B
  • Text instant messaging like in Slack, Google Hangouts, Skype, Facebook Messenger, etc.

Can I make a group call with more than 2 participants?

Group calling is possible to develop with WebRTC and PubNub, however, the current PubNub JS WebRTC package can connect only 2 users in a private call. The community may develop this feature in the future but there are no plans for development to date.

I found a bug in the plugin. Where do I report it?

The PubNub JS WebRTC package is an open source, community supported project. This means that the best place to report bugs is on the GitHub Issues page for the code repository. The community will tackle the bug fix at will, so there is no guarantee that a fix will be made. If you wish to provide a code fix, fork the GitHub repository to your GitHub account, push fixes, and make a pull request (process documented on GitHub).

For more examples, check out the PubNub Tutorials page. If you like this plugin, need some help, or want to build something similar, reach out to We want to hear your feedback!

More from PubNub

How to Create a Dating App: 7 Steps to Fit Any Design
Insights6 minMar 15, 2023

How to Create a Dating App: 7 Steps to Fit Any Design

There are common underlying technologies for a dating app, and in this post, we’ll talk about the major technologies and designs...

Michael Carroll

Michael Carroll

How to Create a Real-time Public Transportation Schedule App
Build6 minMar 14, 2023

How to Create a Real-time Public Transportation Schedule App

How to use geohashing, JavaScript, Google Maps API, and BART API to build a real-time public transit schedule app.

Michael Carroll

Michael Carroll

How to Create Real-Time Vehicle Location Tracking App
Build2 minMar 9, 2023

How to Create Real-Time Vehicle Location Tracking App

How to track and stream real-time vehicle location on a live-updating map using EON, JavaScript, and the Mapbox API.

Michael Carroll

Michael Carroll

Talk to an expert