🎉 New release for Pusher Chatkit - Webhooks! Extend your in-app chat functionality

Extensible API for in-app chat


Build scalable realtime features


Programmatic push notifications



Read the docs to learn how to use our products


Explore our tutorials to build apps with Pusher products


Reach out to our support team for help and advice

Sign in
Sign up

Integrating e-signatures in chat apps with Chatkit and DocuSign

  • Esteban Herrera
November 29th, 2018
You will need Node 6+ installed on your machine. Some knowledge of Node and React will be useful.

In this tutorial, we are going to build a chat application where the users cannot send messages until they sign a non-disclosure agreement (NDA) electronically.

In some industries, a common requirement is to protect the confidentiality of the information disclosed between two parts.

One way to implement this requirement is by using secure and legally-binding electronic signatures.

First, users have to enter their username:

If they haven’t signed the NDA document, the application won’t let them send messages:

When they click on the button at the bottom-right corner, the application will send the NDA to their email:

Here’s a sample email requesting the signature:

Once they have signed the document, DocuSign will call a webhook in the application to allow the users to send messages:

The chat application is built using React and Chatkit. On the server-side, the Node.js SDKs of Chatkit and DocuSign are used.

We’ll also use localtunnel to expose the server so DocuSign can call the webhook when the document has been signed.

The tutorial will focus on the integration between Chatkit and DocuSign, so for the chat application, we’ll start from an adapted version of the chat app built by Alex Booker in this video tutorial. If you want to know more about building chats with Chatkit, you can follow that tutorial.

However, for reference, the entire source code for this project is available on GitHub.


Here’s what you need to have installed/configured to follow this tutorial:

I also assume that you are familiar (an intermediate level at least) with:

  • React
  • Node.js

Let’s start by creating a Chatkit application.

Creating a Chatkit application

Create a free account for Chatkit here and log in.

In your dashboard, create a new Chatkit instance and then, take note of your Instance Locator identifier and Secret Key from the Credentials section, we’re going to need them later.

To keep things simple, I’m going to create two users and a room for the chat using the dashboard instead of using the API or the Node.js Server SDK.

In the Console section of your instance, create two users using the Instance Inspector. For this example, I’ll use the identifiers user1 and user2. Of course, you can use any identifiers you want, and optionally, you can also give them a display name:

Next, in the User Actions menu, click on the Create and join a room link to create a room. Give it a name and take note of the Room ID because we are going to need it too:

You don’t need to add the other user to the room. Chatkit will add it automatically the first time a user uses the chat.

And that’s all the configuration we need for Chatkit.

Now let’s set up the chat application.

Setting up the chat application

Clone the starter project from this GitHub repository.

In a terminal window, cd into the project’s directory and execute npm install to download the dependencies of the project.

Open the project in a JavaScript editor and at the top of the file src/App.js replace your Chatkit instance locator and enter http://localhost:3001 as the value of SERVER_URL.

And in src/ChatScreen.js replace your Chatkit room ID.

Now to customize the application for this project, open the file src/components/SendMessageForm.js and in the render method, below the input box for the message, add the button for sending the NDA. This is what the method should look like:

    render() {
        const styles = {
          container: {
            padding: 20,
            borderTop: '1px #4C758F solid',
            marginBottom: 20,
          form: {
            display: 'flex',
          input: {
            color: 'inherit',
            background: 'none',
            outline: 'none',
            border: 'none',
            flex: 1,
            fontSize: 16,
        return (
          <div style={styles.container}>
              <form onSubmit={this.onSubmit} style={styles.form}>
                  placeholder="Type a message here then hit ENTER"
                  value="SEND NDA"

Next, add the handler function for the onClick event:

    class SendMessageForm extends Component {
      constructor(props) {
        this.onClick = this.onClick.bind(this)

      onClick(e) {
        if (this.props.onClick) {


Now in the file src/ChatScreen.js, add the onClick property to the SendMessageForm component in the render method:

    render() {

        return (
          <div style={styles.container}>
            <div style={styles.chatContainer}>
              <section style={styles.chatListContainer}>

And the sendDocusignRequest method along with the with the SERVER_URL variable:

    const SERVER_URL = 'http://localhost:3001';

    class ChatScreen extends Component {
      constructor(props) {
        this.sendDocusignRequest = this.sendDocusignRequest.bind(this)

      sendDocusignRequest() {
            method: "GET",
        .then(res => res.json())
        .then(res => {
          if(res.error_description) {
          } else {
            alert("The NDA has been sent to your email");
        .catch(err => alert(err));

This method will call the /sign/:userID endpoint on the server to initiate the process of sending the NDA document to the user’s email.

Now the only thing is left is to modify the way messages are posted.

Right now, the JavaScript Chatkit SDK is used to post a message from the client-side:

    sendMessage(text) {
          roomId: this.state.currentRoom.id,

But we need to send the messages from the server to be able to allow the user to send messages only after signing the NDA.

So in the file src/ChatScreen.js, replace the above method with the following:

    sendMessage(text) {
        fetch(`${SERVER_URL}/message/${this.state.currentRoom.id}/${this.state.currentUser.id}`, {
            method: "POST",
            headers: {
              'Content-Type': 'application/json'
            body: JSON.stringify({text: text})
        .then(res => res.json())
        .then(res => {
          if(res.error_description) {

And now this method will call the endpoint /message/:roomID/:userID to send the message using the Chatkit API from the server.

Let’s build the server for the application.

Setting up the server

We are going to create the server in the same project so in a terminal window, execute the following two commands under the project’s directory to download all the dependencies we are going to need:

    npm install --save @pusher/chatkit-server cors docusign-esign express path xml2js
    npm install --save-dev concurrently

Next, in the package.json file, modify the start script to use concurrently to start the server and the chat app at the same time:

      "scripts": {
        "start": "concurrently \"node ./server.js\" \"react-scripts start\"",

And at the root of the directory, create the file server.js.

At the top of this file, let’s define all the libraries we’re going to use:

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const request = require('request');
    const Chatkit = require('@pusher/chatkit-server');
    const docusign = require('docusign-esign');
    const path = require('path');
    const xmlParser = require('xml2js');

Next, define the following constants for Chatkit, replacing your Instance Locator and Secret Key:

    // Chatkit info

    const CHATKIT_API_URL = `https://us1.pusherplatform.io/services/chatkit/v2/${CHATKIT_INSTANCE_ID}`;

Next, define one or two users in the following way:

    // User info (must match the users registered in Chatkit)
    const users = [
        email: 'A_VALID_EMAIL_FOR_USER_1',
        name: 'User 1',
        token: null,
        hasSigned: false,
        email: 'A_VALID_EMAIL_FOR_USER_2',
        name: 'User 2',
        token: null,
        hasSigned: false,

We’ll use this array as our user database.

The only requirements are:

  • The user’s IDs must be the ones you have registered in your Chatkit instance
  • Email addresses must be valid and different for each user

Next, instantiate the Chatkit and Express objects:

    const chatkit = new Chatkit.default({
      instanceLocator: CHATKIT_INSTANCE_LOCATOR,

    const app = express();
    app.use(bodyParser.urlencoded({ extended: false }));

Right now, we are going to configure two endpoints.

The first one is for authentication:

    app.post('/authenticate', (req, res) => {
      const user = users.find(u => u.id === req.query.user_id);

      if(user) {
        const authData = chatkit.authenticate({ userId: req.query.user_id });
        if(authData.status === 200) {
          user.token = authData.body.access_token;
      } else {
        res.status(401).send("The user doesn't exist");

Using the ID received in the request, we search the user in the users’ array and if it is found, using the Chatkit Node.js server SDK, we create an authentication token that is saved in the user’s object. Otherwise, an error is sent to the client.

The second one is for posting the chat messages:

    app.post('/message/:roomID/:userID', (req, res) => {
      const {roomID, userID} = req.params;

      const user = users.find(u => u.id === userID);

      if(user) {
        if(user.hasSigned) {
            url: `${CHATKIT_API_URL}/rooms/${roomID}/messages`, 
            json: {text: req.body.text},
            headers: {
              'Authorization': `Bearer ${user.token}`
          }, (err, httpResponse, body) => {
        } else {
          res.status(401).send({ error_description: "You haven't signed the NDA" });
      } else {
        res.status(401).send({ error_description: "The user doesn't exist" });

At the time of this writing, the server SDK doesn’t have a function to send the message like the client SDK, so we have to call the Chatkit API directly (but only if the hasSigned flag is set).

Now before continuing building the server, let’s set up our DocuSign account.

Setting up DocuSign

First, sign up for a free developer sandbox account.

We’ll need an Integrator Key, so in your dashboard, select Go to Admin from the profile menu in the top right:

On the left side of the Admin Console select API and Keys:

If, for security, the page asks you to log in again, do it and then click the Add App / Integrator Key button. The following window will appear:

Enter an app description and click Add. Next, the following window will appear:


  • A Redirect URI. Anyone will do, for example, https://www.docusign.com.
  • An RSA key pair. Be sure to copy your keys to a safe location because they will not be displayed again (including the ----BEGIN RSA PRIVATE KEY---- and ----END RSA PRIVATE KEY----- lines as part of the text).

Click Save.

Copy your API Username and Integrator Key, we’re going to need them later:

DocuSign supports three authentication methods:

For applications like the one of this tutorial, JSON web tokens are the most appropriate option.

This method works by impersonating a user to make API calls, so the user has to grant permissions for this.

To do this, we have to enter a URL in a new browser window. This is the syntax of the URL:


For a production environment, you must use a base URI of https://account.docusign.com/oauth/auth rather than https://account-d.docusign.com/oauth/auth.

The redirect URI must exactly match the one entering when adding the Integrator Key.

For example, if my Integrator Key is 348e4dd6-d363-40c4-9f93-x000x000000x:

The URL will be:


When you enter the URL, you’ll be asked to grant permissions to the application:

You only have to do this once (as long as you don’t revoke the permissions).

Now the only thing missing in the configuration is creating the template for the document the users will sign.

Either create a PDF document or use this sample document.

Go to the Templates section and create a new template. Give it a name and upload your document:

Next, add a recipient role, for example, user, of type Needs to Sign:

Optionally, add a default email subject and message:

Once you’re done, on the upper right corner click on the Next button and, at least, add a Signature field where you want the users to sign. For example, I added a Signature and a Date Signed field to the sample document:

When you’re done, on the upper right corner click on the Save and Close button.

We’ll need the template ID. To get it, in the Templates section click on the template you just added and in the detail page, next to the template’s name, click on the info icon:

And now we’re ready to integrate the DocuSign API to the server.

Integrating the signature process

In the server.js file, after the require statements, add the following constants, replacing the values of the constants INTEGRATOR_KEY, USER_ID, TEMPLATE_ID, and TEMPLATE_ROLE with the information of the previous section:

    // DocuSign info
    let loginAccount;

    // lt --subdomain YOUR_SUBDOMAIN --port 3001
    const WEBHOOK_URL = 'https://YOUR_SUBDOMAIN.localtunnel.me/webhook';

    const BASE_URL = 'https://demo.docusign.net/restapi';
    const OAUTH_BASE_URL = 'account-d.docusign.com'; // use account.docusign.com for prod
    const PRIVATE_KEY_FILE = 'keys/docusign_private_key.txt';

For the subdomain in the WEBHOOK_URL constant, define the one you’ll be using with localtunnel. For example, I’ll be using chatkit-docusign, so the value of the constant should be:

    const WEBHOOK_URL = 'https://chatkit-docusign.localtunnel.me/webhook';

Next, we need to copy the private key of our Integrator Key into the file keys/docusign_private_key.txt (the value of the constant PRIVATE_KEY_FILE).

So at the root directory create the keys directory and inside of it, the file docusign_private_key.txt with your private key, including the -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- parts, like this:

    -----END RSA PRIVATE KEY-----

Back to the file server.js, let’s define the endpoint to send the NDA at the end of the file:

    app.get('/sign/:userID', (req, res) => {
      const {userID} = req.params;

      const user = users.find(u => u.id === userID);

      if(user) {
        // Webhook notifications from DocuSign
        const envelopeEvents = [];
        let envelopeEvent = new docusign.EnvelopeEvent();
        envelopeEvent.envelopeEventStatusCode = 'Sent';
        envelopeEvent = new docusign.EnvelopeEvent();
        envelopeEvent.envelopeEventStatusCode = 'Completed';

        const recipientEvents = [];
        let recipientEvent = new docusign.RecipientEvent();
        recipientEvent.recipientEventStatusCode = 'Sent';
        recipientEvent = new docusign.RecipientEvent();
        recipientEvent.recipientEventStatusCode = 'Completed';

        const eventNotification = new docusign.EventNotification();
        eventNotification.url = WEBHOOK_URL;
        eventNotification.loggingEnabled = true;
        eventNotification.envelopeEvents = envelopeEvents;
        eventNotification.recipientEvents = recipientEvents;

        // create a new envelope object that we will manage the signature request through
        const envDef = new docusign.EnvelopeDefinition();
        envDef.emailSubject = 'Please sign this document to start using the chat';
        envDef.templateId = TEMPLATE_ID;
        envDef.eventNotification = eventNotification;

        // create a template role with a valid templateId and roleName and assign signer info
        const templateRole = new docusign.TemplateRole();
        templateRole.roleName = TEMPLATE_ROLE;
        templateRole.name = user.name;
        templateRole.email = user.email;

        // assign template role(s) to the envelope
        envDef.templateRoles = [templateRole];

        // send the envelope by setting |status| to 'sent'. To save as a draft set to 'created'
        envDef.status = 'sent';

        // use the |accountId| we retrieved through the Login API to create the Envelope
        const accountId = loginAccount.accountId;

        // instantiate a new EnvelopesApi object
        const envelopesApi = new docusign.EnvelopesApi();

        // call the createEnvelope() API
        envelopesApi.createEnvelope(accountId, {'envelopeDefinition': envDef}, (err, envelopeSummary, response) => {
          if (err) {
            res.status(401).send({ error_description: err });
          console.log('EnvelopeSummary: ' + JSON.stringify(envelopeSummary));
      } else {
        res.status(401).send({ error_description: "The user doesn't exist" });

It’s a big method.

But basically, what it does is the following:

  • Search the user by ID
  • If it finds the user:
    • Set the events for which DocuSign will call the webhook of our server. In this case, when the document is sent and completed.
    • Create the envelope object to get the signature request and set the properties for the email, template, notifications, and status (must be sent).
    • Instantiate an EnvelopesApi object to send the request and sent an error or a success status to the client
  • Otherwise, send an error to the client.

Next, here’s the webhook definition:

    app.post('/webhook', bodyParser.text({
            limit: '50mb',
            type: '*/xml'
    }), (req, res) => {
      console.log("Webhook request body: " + JSON.stringify(req.body));

      xmlParser.parseString(req.body, (err, xml) => {
        if (err || !xml) {
            throw new Error("Cannot parse Connect XML results: " + err);
        const envelopeStatus = xml.DocuSignEnvelopeInformation.EnvelopeStatus;

        if (envelopeStatus[0].Status[0] === 'Completed') {
          const email = envelopeStatus[0].RecipientStatuses[0].RecipientStatus[0].Email[0];
          console.log('Completed email: ' + email);
          const user = users.find(u => u.email === email);
          if(user) {
            user.hasSigned = true;


The webhook receives the information of the event in XML format.

We just need to look up for the Status field and if it has the value Completed, using the signer’s email, update the hasSigned flag in the users’ array.

Here’s the XML data for a sample completed event:

    <?xml version="1.0" encoding="UTF-8"?>
    <DocuSignEnvelopeInformation xmlns="http://www.docusign.net/API/3.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <DeclineReason xsi:nil="true" />
                <CustomFields />
                      <TabLabel>Signature 56f4f270-4e55-4d42-9a7a-51ce88ffd632</TabLabel>
                      <TabValue />
                      <TabLabel>Date Signed 6c88d808-2c06-43dc-b8fe-83d4c836ffde</TabLabel>
                         <field name="DateSigned">
          <Subject>Please sign this document to start using the chat</Subject>
          <UserName>Esteban Herrera</UserName>
          <ACHolder>Esteban Herrera</ACHolder>
          <EnvelopePDFHash />
          <CustomFields />

Finally, to use DocuSign’s API, we need to ask for a JSON Web Token and then look up for the user’s URL to make the API requests. Here’s the code for that:

    const apiClient = new docusign.ApiClient();

    // Get an access token and store it
    apiClient.configureJWTAuthorizationFlow(path.resolve(__dirname, PRIVATE_KEY_FILE), OAUTH_BASE_URL, INTEGRATOR_KEY, USER_ID, TOKEN_EXPIRATION_SECONDS, (err, res) => {
      if (!err && res.body && res.body.access_token) {
        apiClient.getUserInfo(res.body.access_token, function (err, userInfo) {
          const baseUri = userInfo.accounts[0].baseUri;
          const accountDomain = baseUri.split('/v2');
          // below code required for production, no effect in demo (same domain)
          apiClient.setBasePath(accountDomain[0] + "/restapi");
          console.log('LoginInformation: ' + JSON.stringify(userInfo.accounts));

          loginAccount = userInfo.accounts[0];

          const PORT = 3001;
          app.listen(PORT, err => {
            if (err) {
            } else {
              console.log(`Running on port ${PORT}`);

If the login process is successful, the Express server is started on port 3001.

And that’s it.

Now let’s test the application.

Testing the chat

Start the application and the server with npm start.

Then, in another command window, execute:

    npm install -g localtunnel 

To install or update localtunnel.


    lt --subdomain chatkit-docusign --port 3001  

To expose your local server to the world (change your subdomain if needed).

Now enter the chat application and click on the Send NDA button.

You should receive an email with the signature request and after a few seconds (usually less than 30 seconds), DocuSign should call your webhook with the Sent event and the output of your server console should look like this:

    > concurrently "node ./server.js" "react-scripts start"

    [0] LoginInformation: [{"accountId":"7e0af7a8-2ba2-4714-ab4c-fdac9bedecbd","isDefault":"true","accountName":"Esteban Herrera","baseUri":"https://demo.docusign.net"}]
    [0] Running on port 3001
    [1] Starting the development server...
    [1] Compiled successfully!
    [0] EnvelopeSummary: {"envelopeId":"27502bf6-5caf-4b87-b084-1a81685a0af2","status":"sent","statusDateTime":"2018-10-25T22:24:38.3885079Z","uri":"/envelopes/27502bf6-5caf-4b87-b084-1a81685a0af2"}
    [0] Webhook request body: "<?xml version=\"1.0\" encoding=\"utf-8\"?><DocuSignEnvelopeInformation xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.docusign.net/API/3.0\"><EnvelopeStatus><RecipientStatuses><RecipientStatus>...

If you open your DocuSign dashboard, you should see the document it was just sent in the Manage section:

Follow the link in the user’s email to sign the document:

And after you click on Finish, you’ll receive an email telling you that the document was completed and a few seconds later, (once again, usually less than 30 seconds), DocuSign should call your webhook with the Completed event:

    [0] Webhook request body: "<?xml version=\"1.0\" encoding=\"utf-8\"?><DocuSignEnvelopeInformation xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.docusign.net/API/3.0\"><EnvelopeStatus><RecipientStatuses>...
    [0] Completed email: estebanhb2@yahoo.com.mx

In the chat application, you should be able to send messages.


In this tutorial, you have learned how to use the Chatkit Node.js server SDK to authenticate users and Chatkit’s API to post messages applying custom business logic.

About Docusign’s API, you have learned how to authenticate using JSON Web Tokens, configure a template, send a signature request using templates, and configure webhooks to receive event notifications.

I kept the application as simple as possible, but you can improve it in many ways:

  • Disabling the Send NDA button once the user has signed the document.
  • Configure another user in Chatkit, something like a bot, to notify the users the moment they can start sending messages.
  • Improve the design of the chat.
  • Use DocuSign APIs in more creative ways. For example, create a document with the chat messages (instead of using a template) and send it to the participants of the chat for signing.

Here’s a list of resources if you want to learn more about Chatkit and DocuSign:

Remember that you can find the entire source code of the project on this GitHub repository.

Clone the project repository
  • Chat
  • JavaScript
  • Node.js
  • React
  • Chatkit


  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.