Add Video Capabilities to Zendesk With Vonage Video API

Published September 08, 2020 by Javier Molina Sanz And Svetlana Nazarenko

In this tutorial, we’re going to add video, screen sharing and recording functionality to Zendesk by using Vonage Video API so that you can offer a richer customer experience.

You may be thinking that this is not for you as you don’t use Zendesk, but, in fact, there are many other ticketing systems where you could apply these takeaways. If that didn’t convince you, let us show you how to programmatically handle recordings and upload them to a Zendesk ticket so both parties can download it.

The Scenario

  • The customer would like to discuss an outstanding ticket with the support engineer. She requests a video call with the Support engineer by hitting the Discuss Live with Javier button and waits for him to join.

A customer is requesting a call with the support agent

  • The ticket is updated with an internal comment, so the support engineer is notified that the ticket’s requester would like to have a video session.

Agent receives a notification that the customer requests a call

  • The support engineer joins the session, they go through the ticket (not much to discuss in this particular case 😂). They decide to record the call, and once the recording is stopped, it gets uploaded in the form of a ticket comment so both participants can download it.

A recording of a video call between client and support engineer

If this got your attention, please follow along.


To give a high overview of this integration’s architecture, we would like to share the following diagram with you:

On one side, the end-customer is requesting a video call with the support engineer via the Zendesk Request Page. The server will handle the request and will update the ticket to get the Agent’s attention. On the other side, the Agent using Zendesk will join the same session to discuss live.


Before we get started, you will need the following:
1. A Vonage Video API account, which you can create for free
2. Node.js installed and some basic JavaScript knowledge
3. A Zendesk account with administrator rights
4. The Zendesk App Tools (ZAT) installed
5. An Amazon S3 account

Zendesk Agent

To begin with Zendesk applications, you can follow their Build Your First Support App tutorial. Move into your project directory and run the following command.

You will be prompted with some information such as the name of your application; we will call it Zendesk Video App. It will also ask for your email and some other parameters that won’t affect functionality. Once the command gets executed, you will see that the application is created. We’re going to make a folder for our server as well. The final project structure looks like this.

Our application will be made up of a frame embedded into the Zendesk interface, and it will have a video chat area with several actions available. Let’s edit the iframe.html file by adding some simple button elements which will allow the Agent to have a video call with the customer inside of the ticket. You can copy-paste the following code into your iframe.html:

We will add some basic CSS for the buttons as well.

Now, edit the main.js file instantiating a ZAF client. The ZAF client lets your app communicate with the host Zendesk product. You can use the client in your apps to listen for events, get or set properties, or invoke actions. In this case, we’re interested in the details about the ticket we’re working on. In particular, ticket ID and requester ID. Once the promise is fulfilled, we can send a request to our server to get the API key, session ID, and a token for this ticket. All the session generation logic will come from our server. We’ll get to that later on.

Now that we’ve got these values, we can let the Agent choose when to initiate the video session. We will define an initializeSession function that will be triggered once the Agent clicks on the Initiate session button. We will set the publisher container display to block to make it visible (as it’s initially set to none). We will start the session by instantiating a session object, and then we initialize the publisher.

We’ll also create some listeners for events that are dispatched by the session object. We’ll leverage the archiveStarted and archiveSopped events to control our application’s state, i.e., to know whether we’re publishing the video or it’s turned off if we’re recording.

We will display a different value in the HTML buttons, depending on the state. For example, once we receive the archiveStarted, we’ll want our button to read “Stop Archive” rather than “Start Archive” as the Archive/Recording is already initiated.
At the top of our code, we’ve defined some state variables (archiving, video, and screen) that will change based on these events.

We will also want to subscribe to a stream as soon as it’s created, so we will listen for the streamCreated event.

The handleError function we’re passing as a callback is a function that throws an alert if an error happens while listening for events on the session.

We can create a handleRecording function that will determine whether we’re already recording or not. This will allow us to trigger a different function depending on the state.

The StartArchive function will make a POST request to our server’s archive/start route. We need to pass our sessionId so that our server knows which session is triggering the recording. You will see later in the tutorial that we refer to the recording and storing of the session. Do not get confused; it’s the same concept, but we use the term “archive” internally 🙂

As for the StopArchive function, it’s pretty much the same as StartArchive. But, in this case, we need to pass the archiveID that comes from the archiveStarted event.

Now we need to add support for screen sharing streams. We’re going to create a function that will check if we’re sharing our screen already and if not, it will create a new publisher. This function will act as a toggler for the screen share stream in conjunction with some events, just like we did for the archiving.

We’re going check if the browser supports Screen Sharing by calling the OT.checkScreenSharingCapability method. We explain more about screen sharing support in the documentation about checkScreenSharingCapability callback. For some older browser versions, you may need to install an extension, but we will assume that both participants will be using a recent browser for the sake of simplicity.

Note that the events we’re listening to in this case are dispatched by the publisher object rather than the session object. Refer to the StreamEvent for more information.

Customer Side

Now that we’ve got our agent side up and running, we need to think about adding Video capability to the customer’s side. The main purpose of this post is to get the end customer (ticket requester) and the support agent (ticket assignee) connected.

To do that, we’re going to follow customizing your help center theme guide so that we can gain access to the ticket requester’s page code and build a richer customer experience in the Help Center.

In this case, we’re interested in customizing the Requests page, that is, the lists of requests or tickets assigned to a specific user. As explained in the article linked above, the HTML for the Help Center is contained in editable templates. We’re going to be editing the requests_page.hbs file. The code is going to be very similar to the JavaScript code in the main.js file.

First of all, we’re going to import the Opentok library. This will download the latest version of the JS SDK.

We’re adding some basic markup that will contain the publisher and the subscriber video as well as some buttons that will handle the functionality of our application. You will have noticed that we have {{assignee.avatar_url}}. That’s a template language called Curlybars that will allow us to interact with Help Center Data in the context of Zendsk ticket.

In this example, we are displaying a picture of the ticket assignee on the button that will initiate the video call. The aim is to offer a close experience to the customer. Also, to keep it simple at first, we will be hiding all the buttons but the one that initiates the call. We’ll do that by setting the display property of our HTML elements to none.

We’re going to define some variables that we’ll be using throughout the code. As we did for the Agent’s side, we will be working with some state variables (video, archiving, and screenSharing). We will also define the endpoint of our server.

We’re defining a simple error handler function that we will use to alert the user in the event of an error. The only goal of defining this as a separate function is to clean up our code a little bit.

We’re fetching apiKey, sessionId, and token from our server.

Then, add the following initializeSession function, which will be triggered once the customer decides to request a video call with the support agent. We will show the buttons that were hidden at first, then we’re first instantiating a session object and creating a publisher. Lastly, we’re trying to connect to the session. If the connection is successful, we will try to publish to the session, as explained previously.

We’re going to leverage ternary operators to decide whether we need to turn the video on or off. The same logic applies to determine if we’re going to call the function to start the recording or to stop it.

The startArchive() and startArchive() functions look exactly the same as in the main.js, so we’ll omit them for the sake of simplicity. You may also want to just give the option to initiate recordings to the support agent and not to the end customer, but this is totally up to you. To make it more fun, we’ll allow both to initiate and stop recordings as both of them will be able to retrieve the recording after the call.


Our server-side will be composed of several routes to handle the requests coming from either the Agent or the support engineer.

Let’s import the modules that we’re going to be using for our application and define some environment variables.

apiKey and apiSecret are the Video API credentials found in your dashboard, the remoteUri makes reference to the Zendesk endpoint of your organization in the form of For the Zendesk authentication, check out their “How can I authenticate API requests” article as they support different authentication methods; we used username and token.

As for the authentication with AWS, there are several supported methods, but we also decided to go for environment variables. Note that in this case, The SDK automatically detects AWS credentials set as variables in your environment and uses them for SDK requests, eliminating the need to manage credentials in your application. That’s why we’re no reading the variables from our .env file.

Add this to your index.js file.

The route that handles session and tokens creation is going to check if there’s a session already created to discuss this ticket, and if not, it will create one. In case you’re not familiar with the concept of token for the Video API, it is like a key to the room (session).

You would want to have a more secure solution, but we decided to do some basic validation here to keep it simple. In this case, we’re receiving a name parameter in the following format XXXXXX-YYYYY. Do you Remember those fetch calls that we made in both parts (Agent and Customer)? It’s coming from there.

We will only generate a session and a token if the requester ID of the ticket matches the second part of our :name parameter received. We’re going to use a Zendesk package to perform the validation. As an example, if we receive 1222-1234, we will check via Zendesk API if indeed ticket 1234 was requested by user 1222. If not, we will return an HTTP 404.

You will also see that there’s some validation around the referer and the origin of the request. That’s a quick hack done to update the ticket only if the request comes from the customer, and let the support engineer know that the ticket requester would like to have a video session.

In a real-world application, you would probably need to store the session IDs in your database and check if a session has already been created for this ticket. However, we decided to simply use a dictionary that stores session IDs associated with a room name for this tutorial. Bear in mind that this will be reset once you restart your server.

As we mentioned, we will create a session only if there’s no session associated with the room name received. We’re wrapping the callback-based method in a promise that will return a session object.

We’ve also wrapped in a promise the Zendesk check that allows us to query the ticket ID that we have received so we can determine whether the request is legitimate or not.

If the request is valid and comes from the Customer side (not from the Agent), update the ticket so that the support engineer is notified about someone waiting for a video session.

We’re defining the routes to start and stop archive. Note that the route to stop the archiving also takes the session ID. This is, so our servers know which session ID you’re trying to stop the recording for.

If you run your server, expose it with ngrok, and configure the ngrok URL as SERVER_BASE_URL in both front ends (Customer and Agent side). You now have a video session, well done!

Okay, that was cool, but let’s go one step further! Wouldn’t it be great if we could also dynamically handle the call recording and upload it to Zendesk so both the support engineer and customer could retrieve it at their best convenience? Let’s do that!


Handling Recordings

First, we have to let the Video API know where we want our video recording uploaded. As we’re going to use an AWS S3 endpoint, you can follow our Using S3 storage with Vonage Video API archiving guide. Once configured, if you have a video session and you initiate and stop a recording, it will be automatically uploaded to your S3 bucket.

All archives are saved to a subdirectory of your S3 bucket that has your OpenTok API key as its name, and each archive is saved to a subdirectory of that, with the archive ID as its name. The archive file is archive.mp4.

For example, consider an archive with the following API key and ID:

  • API key — 123456
  • Archive ID — ab0baa3d-2539-43a6-be42-b41ff1488af3

The file for this archive is uploaded to the following directory your S3 bucket:


Next, we need to know when the archive has been uploaded to our S3 bucket so we can retrieve it. We’re going to configure a route in our server to listen to archive-related events. The Video API platform will send you a webhook to your previously-configured callback URL when an archive’s status changes.

Go to your dashboard, hit on the project you’re using, and configure your server URL to https://YOUR_SERVER_URL/events. As explained in the archiving guide, the video API platform will send you an available status once the archive is available for download from the S3 bucket. We’ll listen to that event on our server and download it. All of the logic is going to be handled on the server-side (server.js file).

Remember to configure your server URL in your Video API account. Otherwise, you won’t receive these webhooks on your server. It should look something like the following:

Callback URL

We will pass two variables to the downloadVideo function; one is the name that we want our archive to be downloaded with, and the other one is the Key, so our S3 bucket knows what recording we’re trying to retrieve.

The request will stream the returned data directly to a Node.js Stream object by calling the createReadStream method on the request. Calling createReadStream returns the raw HTTP stream managed by the request. The raw data stream can then be piped into a Node.js Stream object. We should now be able to download the recordings dynamically once uploaded to our bucket.

You will have noticed that we’re calling a getToken function once we’re done downloading the file. That’s due to the process of uploading a file to Zendesk. You could do whatever you want with the file at this point, as it’s already downloaded. However, to complete our post, let’s upload the recording to the Zendesk ticket so both participants can watch the recording after the call.

We first need to get a token, and then we need to update the ticket passing this token. We’ll do the second part in a separate function called uploadVideo.

Check out the demo to get a better idea of how all this works. Adapt this tutorial to suit your needs, leave your customers highly satisfied and true advocates of the support experience.

Find the code for this project in the vonage-zendesk-integration GitHub repo.

What will you build next? Let us know!

Leave a Reply

Your email address will not be published.

Get the latest posts from Nexmo’s next-generation communications blog delivered to your inbox.

By signing up to our communications blog, you accept our privacy policy , which sets out how we use your data and the rights you have in respect of your data. You can opt out of receiving our updates by clicking the unsubscribe link in the email or by emailing us at [email protected].