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.
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}}.
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.