You can create complex documents with SpreadsheetWeb applications using Microsoft Word templates. This tutorial explains how you can embed electronic signatures into those documents using DocuSign electronic signature REST API.

We will demonstrate this integration using a simple SpreadsheetWeb application that allows users to generate and sign a Non-Disclosure Agreement (NDA). The application has a set of input fields such as company name, location, signers name, title, and email. The application’s web UI looks like below.

Inserting DocuSign Electronic Signature into Word Documents Used in SpreadsheetWeb Applications

The underlying Excel file also contains information about the receiving company, such as name and address of the person signing off. Therefore, it is possible to make the signer details dynamic using Excel formulas. A good example of a dynamic structure would be sending the document to a regional VP depending on the disclosing company’s location.

The word template embeds the data captured by SpreadsheetWeb User Interface into the Word template through the help of double curly brackets {{data}}.

Note that this Word template also includes DocuSign electronic signature tags which can identify the location of electronic signatures.

 

DocuSign Electronic Signature REST API

The first step is obtaining a developer key from DocuSign developer site.

https://go.docusign.com/o/sandbox/

Next, insert the developer key information into the sample Custom Action C# code below.

using Newtonsoft.Json;
using Pagos.Designer.Interfaces.External.CustomHooks;
using Pagos.Designer.Interfaces.External.Messaging;
using Pagos.SpreadsheetWeb.Web.Api.Objects.Calculation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Linq;
using System.Net.Security;
using Pagos.Designer.Interfaces.External.DataTransfer;

namespace DocuSignSample
{
    #region EncodingForBase64
    public static class EncodingForBase64
    {
        public static string EncodeBase64(this System.Text.Encoding encoding, string text)
        {
            if (text == null)
            {
                return null;
            }

            byte[] textAsBytes = encoding.GetBytes(text);
            return System.Convert.ToBase64String(textAsBytes);
        }

        public static string DecodeBase64(this System.Text.Encoding encoding, string encodedText)
        {
            if (encodedText == null)
            {
                return null;
            }

            byte[] textAsBytes = System.Convert.FromBase64String(encodedText);
            return encoding.GetString(textAsBytes);
        }
    }
    #endregion

    #region TokenObject
    public class TokenObject
    {
        public string access_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string refresh_token { get; set; }

    }
    #endregion

    #region EnvelopeDefinition
    public class EnvelopeDefinition {
        [JsonProperty("emailSubject")]
        public string EmailSubject { get; set; }

        [JsonProperty("documents")]
        public List<Document> Documents { get; set; }

        [JsonProperty("recipients")]
        public Recipients Recipients { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; }
    }
    #endregion

    #region Document
    public class Document {
        [JsonProperty("documentBase64")]
        public string DocumentBase64 { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("documentId")]
        public string DocumentId { get; set; }
    }
    #endregion

    #region Signer
    public class Signer {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("email")]
        public string Email { get; set; }

        [JsonProperty("recipientId")]
        public string RecipientId { get; set; }

        [JsonProperty("tabs")]
        public Tabs Tabs { get; set; }
        /// Specifies the routing order of the recipient in the envelope. 
        /// </summary>
        /// <value>Specifies the routing order of the recipient in the envelope. </value>
        [JsonProperty("routingOrder")]
        public string RoutingOrder { get; set; }

    }
    #endregion

    #region Tabs
    public class Tabs {
        [JsonProperty("signHereTabs")]
        public List<SignHere> SignHereTabs { get; set; }



    }
    #endregion

    #region SignHere
    public class SignHere {
        [JsonProperty("documentId")]
        public string DocumentId { get; set; }

        [JsonProperty("pageNumber")]
        public string PageNumber { get; set; }

        [JsonProperty("recipientId")]
        public string RecipientId { get; set; }

        [JsonProperty("xPosition")]
        public string XPosition { get; set; }

        [JsonProperty("yPosition")]
        public string YPosition { get; set; }

        [JsonProperty("anchorString")]
        public string AnchorString { get; set; }

        [JsonProperty("anchorUnits")]
        public string AnchorUnits { get; set; }

        [JsonProperty("anchorXOffset")]
        public string AnchorXOffset { get; set; }

        [JsonProperty("anchorYOffset")]
        public string AnchorYOffset { get; set; }
    }
    #endregion

    #region Recipients
    public class Recipients {
        [JsonProperty("signers")]
        public List<Signer> Signers { get; set; }
    }
    #endregion

    #region Envelope Response
    public class EnvelopeResponse {
        [JsonProperty("envelopeId")]
        public string EnvelopeId { get; set; }

        [JsonProperty("uri")]
        public string Uri { get; set; }

        [JsonProperty("statusDateTime")]
        public string StatusDateTime { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; }

    }
    #endregion
    public class Class1 : IAfterPrint
    {
        const string CLIENT_ID = "YOUR CLIENT ID";
        const string CLIENT_SECRET = "YOUR CLIENT SECRET;
        const string TOKEN_FILE_PATH = "c:\\docuSign.json";
        const string REDIRECT_URI = "YOUR APPLICATION URL";
        const string ACCOUNT_ID = "YOUR ACCOUNT ID";

        public Class1()
        {
            ServicePointManager.SecurityProtocol =
                SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback
            (
                delegate { return true; }
            );

        }

        #region PostFormData
        public T PostFormData<T>(string url, Dictionary<string, string> headers, Dictionary<string, string> data, string bearerToken = null)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.Timeout = 180000;

            request.Headers["cache-control"] = "no-cache";

            if (!string.IsNullOrEmpty(bearerToken))
            {
                request.Headers["Authorization"] = $"Bearer {bearerToken}";
            }
            if (headers != null) {
                foreach (string k in headers.Keys.ToList()) {
                    request.Headers[k] = headers[k];
                }
            }
            StringBuilder sb = new StringBuilder();
            List<string> allKeys = data.Keys.ToList();

            for (int n = 0; n < allKeys.Count; n++)
            {
                var key = allKeys[n];
                sb.Append(key + "=" + HttpUtility.UrlEncode(data[key]));

                if (n < (allKeys.Count - 1))
                    sb.Append("&");
            }

            var encoding = new UTF8Encoding();
            var content = encoding.GetBytes(sb.ToString());
            request.ContentLength = content.Length;
            request.ContentType = "application/x-www-form-urlencoded";
            using (var dataStream = request.GetRequestStream())
            {
                dataStream.Write(content, 0, content.Length);
            }
            string responseText;

            using (var response = (HttpWebResponse)request.GetResponse())
            using (var stream = response.GetResponseStream())
            using (var reader = new System.IO.StreamReader(stream, Encoding.UTF8))
            {
                responseText = reader.ReadToEnd();
            }
            return JsonConvert.DeserializeObject<T>(responseText);
        }
        #endregion

        #region GetAcccessToken
        void GetAcccessToken() {
            //read code from text file
            string code = File.ReadAllText("c:\\docuSignCode.txt");
            string authorizationCode = System.Text.Encoding.UTF8.EncodeBase64(CLIENT_ID + ":" + CLIENT_SECRET);
            Dictionary<string, string> dHeaders = new Dictionary<string, string>();
            Dictionary<string, string> dBody = new Dictionary<string, string>();
            dBody.Add("grant_type", "authorization_code");
            dBody.Add("code", code);
            dHeaders.Add("Authorization", "Basic " + authorizationCode);
            try
            {
                var result = PostFormData<TokenObject>("https://account-d.docusign.com/oauth/token", dHeaders, dBody);
                File.WriteAllText(TOKEN_FILE_PATH, JsonConvert.SerializeObject(result));
            }
            catch (Exception ex)
            {
            }
        }
        #endregion

        #region RefreshToken
        void RefreshToken()
        {
            string authorizationCode = System.Text.Encoding.UTF8.EncodeBase64(CLIENT_ID + ":" + CLIENT_SECRET);
            TokenObject to = JsonConvert.DeserializeObject<TokenObject>(File.ReadAllText(TOKEN_FILE_PATH));

            Dictionary<string, string> dHeaders = new Dictionary<string, string>();
            Dictionary<string, string> dBody = new Dictionary<string, string>();
            dBody.Add("grant_type", "refresh_token");
            dBody.Add("refresh_token", to.refresh_token);
            dHeaders.Add("Authorization", "Basic " + authorizationCode);
            try
            {
                
                var result = PostFormData<TokenObject>("https://account-d.docusign.com/oauth/token", dHeaders, dBody);
                File.WriteAllText(TOKEN_FILE_PATH, JsonConvert.SerializeObject(result));
            }
            catch (Exception ex)
            {
            }
        }
        #endregion

        #region PostJSON
        public string PostJSON(string url, Dictionary<string, string> headers,  object data, string bearerToken = null)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = "POST";
            request.Timeout = 180000;


            if (!string.IsNullOrEmpty(bearerToken))
            {
                request.Headers["Authorization"] = $"Bearer {bearerToken}";
            }

            if (headers != null)
            {
                foreach (string k in headers.Keys.ToList())
                {
                    request.Headers[k] = headers[k];
                }
            }

            var json = JsonConvert.SerializeObject(data);

            var encoding = new UTF8Encoding();
            var content = encoding.GetBytes(json);
            request.ContentLength = content.Length;
            request.ContentType = @"application/json";
            
            using (var dataStream = request.GetRequestStream())
            {
                dataStream.Write(content, 0, content.Length);
            }
            string responseText;
            try
            {
                using (var response = (HttpWebResponse)request.GetResponse())
                using (var stream = response.GetResponseStream())
                using (var reader = new System.IO.StreamReader(stream, Encoding.UTF8))
                {

                    responseText = reader.ReadToEnd();
                }

                return responseText;
            }
            catch (Exception ex)
            {
                return "";
            }
        }
        #endregion


        public StandardResponse AfterPrint(CalculationRequest request, CalculationResponse response, PrintProcessOutput printProcessOutput)
        {
            RefreshToken();

            var companyName = request.Inputs.FirstOrDefault(x => x.Ref == "iCompany").Value[0][0].Value;
            var s1Name = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature1Name").Value[0][0].Value;
            var s1Title = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature1Title").Value[0][0].Value;
            var s1Email = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature1Email").Value[0][0].Value;

            var s2Name = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature2Name").Value[0][0].Value;
            var s2Title = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature2Title").Value[0][0].Value;
            var s2Email = request.Inputs.FirstOrDefault(x => x.Ref == "iSignature2Email").Value[0][0].Value;

            Dictionary<string, string> dHeaders = new Dictionary<string, string>();
            TokenObject to = JsonConvert.DeserializeObject<TokenObject>(File.ReadAllText(TOKEN_FILE_PATH));
            dHeaders.Add("Cache-Control", "no-cache");
            EnvelopeDefinition envDef = new EnvelopeDefinition();
            envDef.EmailSubject = "Please sign this doc";

            // Add a document to the envelope
            Document doc = new Document();
            doc.DocumentBase64 = System.Convert.ToBase64String(printProcessOutput.FileContents);
            doc.Name = "TestFile.pdf";
            doc.DocumentId = "1";

            envDef.Documents = new List<Document>();
            envDef.Documents.Add(doc);

            // Add a recipient to sign the documeent
            Signer signer1 = new Signer();
            signer1.Email = s1Email;
            signer1.Name = s1Name;
            signer1.RecipientId = "1";
            signer1.RoutingOrder = "1";

            // Create a |SignHere| tab somewhere on the document for the recipient to sign
            signer1.Tabs = new Tabs();
            signer1.Tabs.SignHereTabs = new List<SignHere>();
            SignHere signHere1 = new SignHere();
            signHere1.DocumentId = "1";
            signHere1.RecipientId = "1";
            signHere1.AnchorString = "{signer1signature}";
            signHere1.AnchorUnits = "mms";
            signHere1.AnchorXOffset = "0";
            signHere1.AnchorYOffset = "0";
            signer1.Tabs.SignHereTabs.Add(signHere1);

            Signer signer2 = new Signer();
            signer2.Email = s2Email;
            signer2.Name = s2Name;
            signer2.RecipientId = "2";
            signer2.RoutingOrder = "2";

            // Create a |SignHere| tab somewhere on the document for the recipient to sign
            signer2.Tabs = new Tabs();
            signer2.Tabs.SignHereTabs = new List<SignHere>();
            SignHere signHere2 = new SignHere();
            signHere2.DocumentId = "1";
            signHere2.RecipientId = "2";
            signHere2.AnchorString = "{signer2signature}";
            signHere2.AnchorUnits = "mms";
            signHere2.AnchorXOffset = "0";
            signHere2.AnchorYOffset = "0";
            signer2.Tabs.SignHereTabs.Add(signHere2);


            envDef.Recipients = new Recipients();
            envDef.Recipients.Signers = new List<Signer>();
            envDef.Recipients.Signers.Add(signer1);
            envDef.Recipients.Signers.Add(signer2);

            // set envelope status to "sent" to immediately send the signature request
            envDef.Status = "sent";

            try
            {
                string resp = PostJSON(string.Format("https://demo.docusign.net/restapi/v2/accounts/{0}/envelopes", ACCOUNT_ID), dHeaders, envDef, to.access_token);
            }
            catch (Exception ex) {
            }
            return new StandardResponse()
            {
                Success = true
            };
        }
    }
}

Following Docusign Electronic Signature parameters are used in this application.

EnvelopeDefinition.EmailSubject

EnvelopeDefinition.Status

EnvelopeDefinition.Documents.Document.DocumentBase64

EnvelopeDefinition.Documents.Document.Name

EnvelopeDefinition.Documents.DocumentId

EnvelopeDefinition.Recipients.Signer.Email

EnvelopeDefinition.Recipients.Signer.Name

EnvelopeDefinition.Recipients.Signer.RecipientId

EnvelopeDefinition.Recipients.Signer.RoutingOrder

EnvelopeDefinition.Recipients.Signer.Tabs

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.DocumentId

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.RecipientId

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.AnchorString

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.AnchorUnits

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.AnchorXOffset

EnvelopeDefinition.Recipients.Signer.Tabs.SignHereTabs.SignHere.AnchorYOffset

We will not go into the details of Custom Code class implementation and uploading your Custom Action to a designer application here. Please see related tutorials like Integrate SpreadsheetWeb with Sales Force for more information about these features.

Note: Integration of docusign electronic signature is done using SpreadsheetWeb Custom Actions. Hence it requires either a server or private cloud license.

Note: This tutorial is created using SpreadsheetWeb version 6.5.