Microsoft Word templates can be integrated easily into SpreadsheetWEB applications, and documents containing application and user data can be generated from these templates. In this tutorial we’re going to demonstrate how to embed electronic signatures into Word documents using Adobe Sign electronic signature REST API.

We will be using a simple SpreadsheetWEB application to show you how to do this integration. This application basically allows users to generate and sign a Non-Disclosure Agreement after entering data like company name, location, or signers name. The application looks like below.

Insert Adobe Electronic Signature to Word Documents in SpreadsheetWeb Applications

The receiving company details such as the company name are included in the underlying Excel. Thanks to this flexibility, you can also generate this data dynamically. For example, the records can be sent to the corresponding departments based on the location input. You can download the Excel file from our example below.

The word template embeds data captured by the SpreadsheetWeb User Interface into the Word template fields that are wrapped inside double curly brackets {{data}}.

Since our example populates data based on locations, this Word template also includes AdobeSign electronic signature tags which can identify the location of electronic signatures.

 

Adobe Sign Electronic Signature REST API

First, we need to obtain a developer key from the AdobeSign developer site. https://www.adobe.io/apis/documentcloud/sign.html Then, continue with inserting the developer key information to the sample Custom Action C# code below.

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

namespace AdobeSignApi
{
    #region TokenObject
    public class TokenObject
    {
        public string access_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
    }
    #endregion

    #region RefreshTokenRequest
    public class RefreshTokenRequest
    {
        public string refresh_token { get; set; }
        public string client_id { get; set; }
        public string client_secret { get; set; }
        public string grant_type
        {
            get {
                return "refresh_token";
            }
        }
    }
    #endregion

    #region UploadDocumentResponse
    public class UploadDocumentResponse {
        public string transientDocumentId { get; set; }
    }
    #endregion

    #region MemberInfo
    public class MemberInfo {
        public string email { get; set; }
    }
    #endregion

    #region ParticipantSetInfo
    public class ParticipantSetInfo {
        public ParticipantSetInfo() {
            memberInfos = new List<MemberInfo>();
        }
        public List<MemberInfo> memberInfos { get; set; }
        public int order { get; set; }
        public string role { get; set; }
        public string name { get; set; }
    }
    #endregion

    #region DocumentInfo
    public class DocumentInfo
    {
        public string id { get; set; }
        public string label { get; set; }
        public int numPages { get; set; }
        public string mimeType { get; set; }
        public string name { get; set; }
    }
    #endregion

    #region SendDocumentRequest
    public class SendDocumentRequest {
        public SendDocumentRequest() {
            fileInfos = new List<UploadDocumentResponse>();
            participantSetsInfo = new List<ParticipantSetInfo>();
            document = new DocumentInfo();
        }
        public DocumentInfo document { get; set; }
        public List<UploadDocumentResponse> fileInfos { get; set; }
        public List<ParticipantSetInfo> participantSetsInfo { get; set; }
        public string name { get; set; }
        public string signatureType { get; set; }
        public string state { get; set; }
    }
    #endregion

    #region SendDocumentResponse
    public class SendDocumentResponse {
        public string id { get; set; }
    }
    #endregion

    public class Class1 : IAfterPrint
    {
        const string REFRESH_TOKEN = "YOUR REFRESH TOKEN";
        const string CLIENT_ID = "YOUR CLIENT ID";
        const string CLIENT_SECRET = "YOUR CLIENT SECRET";
        const string TOKEN_FILE_PATH = "c:\\adobe.json";
        const string API_ACCESS_POINT = "https://api.na2.echosign.com";
        public Class1()
        {
            ServicePointManager.SecurityProtocol =
                SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;

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

        }

        #region Upload File
        private UploadDocumentResponse HttpUploadFile(string url, string filePath, string fileName, string accessToken)
        {

            var nvc = new NameValueCollection
                {
                    {"File", filePath},
                    {"Content-Type", "multipart/form-data"},
                    {"Mime-Type", "application/pdf"},
                    {"File-Name", fileName}
                };

            string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
            byte[] boundarybytesF = System.Text.Encoding.ASCII.GetBytes("--" + boundary + "\r\n");  

            HttpWebRequest wr = (HttpWebRequest)WebRequest.Create(API_ACCESS_POINT + "/api/rest/v6/transientDocuments");
            wr.Method = "POST";
            wr.Headers["Authorization"] = $"Bearer {accessToken}";
            wr.KeepAlive = true;
            wr.Credentials = System.Net.CredentialCache.DefaultCredentials;
            wr.AllowWriteStreamBuffering = true;

            wr.ContentType = "multipart/form-data; boundary=" + boundary;

            Stream rs = wr.GetRequestStream();

            rs.Write(boundarybytesF, 0, boundarybytesF.Length);
            string formdataTemplate = "Content-Disposition: form-data; name=\";File\"; filename=\"MyPDF.pdf\"\r\n\r\n";
            byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formdataTemplate);
            rs.Write(formitembytes, 0, formitembytes.Length);

            rs.Write(boundarybytes, 0, boundarybytes.Length);
            string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
            string header = string.Format(headerTemplate, "File", new FileInfo(filePath).Name, "application/octet-stream");
            byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);
            rs.Write(headerbytes, 0, headerbytes.Length);
            FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            byte[] buffer = new byte[fileStream.Length];
            int bytesRead = 0;
            while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
            {
                rs.Write(buffer, 0, bytesRead);
            }
            fileStream.Close();
            byte[] trailer = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
            rs.Write(trailer, 0, trailer.Length);
            rs.Close();

            try
            {
                var httpResponse = (HttpWebResponse)wr.GetResponse();
                using (var sr = new StreamReader(httpResponse.GetResponseStream()))
                {
                    var result = sr.ReadToEnd();
                    return JsonConvert.DeserializeObject<UploadDocumentResponse>(result);

                }
            }
            catch (Exception ex)
            {
                return null;
                
            }
        }
        #endregion

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

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

            if (!string.IsNullOrEmpty(bearerToken))
            {
                request.Headers["Authorization"] = $"Bearer {bearerToken}";
            }
            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 PostJSON
        public T PostJSON<T>(string url, 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}";
            }
            var json = JsonConvert.SerializeObject(data,
                new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });

            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 JsonConvert.DeserializeObject<T>(responseText);
            }
            catch (Exception ex) {
                return default(T);
            }
        }
        #endregion

        #region RefreshToken
        void RefreshToken() {
            RefreshTokenRequest req = new RefreshTokenRequest();
            Dictionary<string, string> dValues = new Dictionary<string, string>();
            dValues.Add("grant_type", req.grant_type);
            dValues.Add("client_id", CLIENT_ID);
            dValues.Add("client_secret", CLIENT_SECRET);
            dValues.Add("scope", "user_login agreement_write:self agreement_send:account");
            dValues.Add("refresh_token", REFRESH_TOKEN);
            
            try
            {
                var result = PostFormData<TokenObject>(API_ACCESS_POINT, "oauth", "refresh", dValues);
                File.WriteAllText(TOKEN_FILE_PATH, JsonConvert.SerializeObject(result));
            }
            catch (Exception ex)
            {
            }
        }
        #endregion

        #region AfterPrint
        public StandardResponse AfterPrint(CalculationRequest request, CalculationResponse response, PrintProcessOutput printProcessOutput)
        {
            TokenObject to = null;
            try
            {
                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;

                to = JsonConvert.DeserializeObject<TokenObject>(File.ReadAllText(TOKEN_FILE_PATH));
                string filePath = "c:\\pswTemp\\" + printProcessOutput.FileName;
                File.WriteAllBytes(filePath, printProcessOutput.FileContents);
                UploadDocumentResponse resp = HttpUploadFile(API_ACCESS_POINT + "/api/rest/v6/transientDocuments", filePath, "file",  to.access_token);
                File.Delete(filePath);
                if (resp != null)
                {
                    SendDocumentRequest req = new SendDocumentRequest();
                    req.name = companyName;
                    req.fileInfos.Add(resp);

                    ParticipantSetInfo part1 = new ParticipantSetInfo();
                    ParticipantSetInfo part2 = new ParticipantSetInfo();

                    part1.memberInfos.Add(new MemberInfo() { email = s1Email});
                    part1.order = 1;
                    part1.role = "SIGNER";
                    part1.name = s1Name;

                    part2.memberInfos.Add(new MemberInfo() { email = s2Email });
                    part2.order = 2;
                    part2.role = "SIGNER";
                    part2.name = s2Name;

                    req.participantSetsInfo.Add(part1);
                    req.participantSetsInfo.Add(part2);
                    req.signatureType = "ESIGN";
                    req.state = "IN_PROCESS";
                    req.document.numPages = 3;
                    try
                    {
                        SendDocumentResponse sr = PostJSON<SendDocumentResponse>(API_ACCESS_POINT + "/api/rest/v6/agreements", req, to.access_token);
                    }
                    catch (Exception ex) {
                    }
                }
                else {
                }
            }
            catch (Exception ex) {
            }

            return new StandardResponse()
            {
                Success = true
            };
        }
        #endregion

    }
}

Following AdobeSign Electronic Signature parameters are used in this application.

SendDocumentRequest.name

SendDocumentRequest.signatureType

SendDocumentRequest.state

SendDocumentRequest.DocumentInfo.numPages

SendDocumentRequest.fileInfos.UploadDocumentResponse.transientDocumentId

SendDocumentRequest.participantSetsInfo.ParticipantSetInfo.order

SendDocumentRequest.participantSetsInfo.ParticipantSetInfo.role

SendDocumentRequest.participantSetsInfo.ParticipantSetInfo.name

We will not go into the details of Custom Code class implementation and uploading your Custom Action to a designer application in this article. Please see our other tutorials like Integrate SpreadsheetWeb with Sales Force for more details on these subjects.

Note: Integration of adobe 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.