DocuSign provides electronic signature technology and digital transaction management through a web app. In the DocuSign lifecycle, the process ends with a signed, voided, or rejected document. Because of this, it’s important to understand how DocuSign structures its documents and maintains signature validity. DocuSign uses the concept of envelopes and documents to allow seamless signing experiences while preserving document independence
An envelope can contain one or more documents, the fields in the document, recipient info, delivery progress, sender information, security and more.
A document is the lowest-level object on which an intact signature seal can be placed. Multiple documents can be added to an envelope. After signing, these documents can then be extracted individually if needed.
Electronic seals are used to show evidence of origin and integrity of documents. One of the ways DocuSign uses seals is to ensure the validity of an electronic signature on a document and to ensure that no document tampering has been done after signing. If a document is split up or changed in any way after signing, the seals will be broken and the signatures are invalidated. If pages or documents need to be independently extracted post signing, to preserve seal integrity, the items need to be split into “documents” on the same envelope.
Setup of DocuSign requires admin access on the DocuSign account. For easy production deployment, you will want the sandbox to be configured with an account linked to your Production Account.
Integration to DocuSign in production requires the promotion of an API key by following DocuSign's Go-Live Steps from each environment. The following guide will highlight setting up a connection for a single Appian environment to send single-document envelopes to a single signer.
There are three common methods for setting up authentication and authorization between Appian and DocuSign.
This method uses a single service account DocuSign user. The integration calls to DocuSign are made as this service account user. The first step necessary is to login to the DocuSign account with Admin privileges that the client wishes to use for their integration and retrieve some key pieces of information.
The easiest way to interact with DocuSign in Appian is through the use of a Connected System.
Rather than using a service account to make the calls from Appian to DocuSign, the user authorizes Appian to make calls to DocuSign on their behalf using their DocuSign account. Please note that this method requires providing the user an authorization link within an Appian SAIL form before making a call on their behalf. If calls on their behalf fail, catch the error, and again provide the user with an authorization link to reauthorize.
As in method 1, this method also uses each individual user’s Docusign account to make the integration calls, however the user is not required to manually authorize Appian to act on their behalf. A DocuSign admin provides organizational consent for all users. This method requires using the JWT Tools plugin to accomplish the generation of access tokens and cannot use the DocuSign connected system for integrations.
This approach is often paired with the Service Account Integration method such that certain calls (Send Envelope) use the JWT Tools plugin and others (Download Document) use the Appian Connected System.
Connectivity can quickly be validated with a GET Account call. This call will retrieve the account’s basic information but does require having the API Account ID
There are a number of actions that a developer can perform on a DocuSign envelope and the documents contained within. An introduction to the concepts of DocuSign’s Object model can be found on their eSignature Object Model Overview. Users of this guide are encouraged to read through DocuSign’s documentation to learn more about what is possible when integrating with DocuSign.
Use the Appian Connected System OOTB operation to Create and Send Envelope which utilizes DocuSign’s Create Envelope REST API. The request body is expressionable in Appian and the DocuSign documentation page linked above has great JSON examples of configurations with different numbers of documents, signers, recipients, tabs, etc.
In Appian, an example expression for a single signer with an embedded signing ceremony might look like this:
a!toJson( { /*documents: - handled by Appian Integration Object*/ /*status: - handled by Appian Integration Object*/ emailSubject: ri!subject, recipients: { signers: { { autoNavigation: true, clientUserId: ri!signer.username, email: ri!signer.email, name: ri!signer.fullName, recipientId: ri!signer.recipientId, recipientSuppliesTabs: false, routingOrder: "1", tabs: { dateSignedTabs: { { anchorString: "today_1_r", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "false" } }, initialHereTabs: { { anchorString: "init_1_r", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "false" }, { anchorString: "init_1_o", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "true" } }, signHereTabs: { { anchorString: "sig_1_r", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "false" }, { anchorString: "sig_1_o ", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "true" } }, textTabs: { { tabLabel: "firstText", anchorString: "text_1_r", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "false" }, { tabLabel: "secondText", anchorString: "text_2_r", anchorXOffset: "0", anchorYOffset: "0", anchorIgnoreIfNotPresent: "true", anchorUnits: "inches", optional: "false" } } } } } } } )
clientUserId - this field should only be populated if the signing ceremony will be embedded in Appian for the recipient. Populate using the Appian username.
recipientId - this field needs to be unique for the recipients within the envelope. If there is only one recipient, use the number 1. If there are multiple, use unique integers for each.
tabs - this implementation uses the anchor string method (see other tab creation methods here and tab types here) for designating input fields for the end user. For example, the document template has transparent white text on a white background of “sig_1_r” where a signature is required.
If clientUserId is not populated, the signing ceremony will be remote. The user will receive an email from DocuSign to perform the signing ceremony in DocuSign.
If clientUserId is populated, the signing ceremony must be embedded. The user will not receive an email nor have the option to perform the signing ceremony in DocuSign, as the application is responsible for the embedded signing experience.
Use the Appian Connected System OOTB operation to Generate the Recipient Signing URL which utilizes DocuSign’s Create Embedded Recipient REST API. Since this operation is a POST call and the DocuSign URL must be used within 5 minutes, the appropriate way to trigger this integration call is on button-click within a SAIL interface each time the user opens the form. It should not be called within a process model since the user may close the task and come back more than 5 minutes later. It cannot be called as part of local variable instantiation since it is a POST call and not a GET call. An example implementation might look like this:
rule!APP_CMPT_requestBoxLayout( label: "Acknowledgement", contents: { a!richTextDisplayField( label: "Rich Text", labelPosition: "COLLAPSED", value: { a!richTextItem( text: { cons!APP_TXT_DS_ACKNOWLEDGEMENT }, style: { "EMPHASIS" } ) } ), a!buttonArrayLayout( buttons: { a!buttonWidget( label: "OK", saveInto: rule!APP_INT_DS_genEmbeddedSigningURL( envelopeId: ri!envelopeId, signer: ri!signer, onSuccess: { a!save( local!embedUrl, index(fv!result, "url", null) ) }, onError: { a!startProcess( processModel: cons!APP_PM_INTEGRATION_ERROR_TASK, processParameters: { integrationError: fv!error, integrationName: cons!APP_TXT_DS_GEN_URL_INT_NAME, isRetryError: false, request: = "Generate signing URL for envelope " & ri!envelopeId, requestId: ri!requestId, response: fv!result } ), a!save(local!generateUrlError, fv!error) } ), style: "PRIMARY" ) }, align: "END" ) }, showWhen: and( rule!GBL_isBlank(local!embedUrl), rule!GBL_isEmpty(ri!createEnvelopeError), rule!GBL_isEmpty(local!generateUrlError) ), style: "STANDARD" )
Return URL - this is the URL that shows in the web content field of Appian after the (1) user completes the signature (2) user declines to sign OR (3) embedded DocuSign session times out after 20 minutes of inactivity, which is the DocuSign default. This URL cannot be an Appian URL, therefore the best option is a public URL hosted on the customer network, specifically designed to provide the end user with a useful message after any one of these three events occurs. The single message should cover all three scenarios since it will be displayed regardless of which event occurs.
Recipient User Name - this field needs to have the same value that you specified in the “name” field while creating the envelope in the Create and Send Envelope operation.
The embedding URL can be placed with Appian’s web content component on a SAIL form. If you provide an Appian submit button on the SAIL form, there is a risk that the user clicks that Appian button before completing the signing ceremony, which requires clicking a “Finish” button on the embedded form. To reduce this risk, prevent the user from submitting the form until the DocuSign Connect Webhook (see below) has informed Appian that the signing ceremony is complete by either:
1. Disabling the “Continue” button in Appian until DocuSign has called the Appian Web API and the application database is updated with a “Signed” status
a!columnLayout( contents: { a!richTextDisplayField( labelPosition: "COLLAPSED", value: a!richTextItem( text: "STEPS", color: "SECONDARY", size: "MEDIUM_PLUS", style: "STRONG" ) ), a!forEach( items: { a!richTextDisplayField( labelPosition: "COLLAPSED", value: { a!richTextItem( text: "Sign", color: "SECONDARY", size: "MEDIUM", style: "STRONG" ), a!richTextItem( text: " the document", color: "SECONDARY", size: "MEDIUM" ) } ), a!richTextDisplayField( labelPosition: "COLLAPSED", value: { a!richTextItem( text: "Click the", color: "SECONDARY", size: "MEDIUM" ), a!richTextItem( text: " Finish", color: "SECONDARY", size: "MEDIUM", style: "STRONG" ), a!richTextItem( text: " button at the top", color: "SECONDARY", size: "MEDIUM" ) } ), a!richTextDisplayField( labelPosition: "COLLAPSED", value: { a!richTextItem( text: "Click this", color: "SECONDARY", size: "MEDIUM" ), a!richTextItem( text: " Refresh", color: "SECONDARY", size: "MEDIUM", style: "STRONG" ), a!richTextItem( text: " button until the", color: "SECONDARY", size: "MEDIUM" ), a!richTextItem( text: " Continue", color: "SECONDARY", size: "MEDIUM", style: "STRONG" ), a!richTextItem( text: " button below is enabled", color: "SECONDARY", size: "MEDIUM" ) } ), a!richTextDisplayField( labelPosition: "COLLAPSED", value: { a!richTextItem( text: "Click the", color: "SECONDARY", size: "MEDIUM" ), a!richTextItem( text: " Continue", color: "SECONDARY", size: "MEDIUM", style: "STRONG" ), a!richTextItem( text: " button below", color: "SECONDARY", size: "MEDIUM" ) } ) }, expression: { a!columnsLayout( columns: { a!columnLayout( contents: a!cardLayout( contents: a!richTextDisplayField( labelPosition: "COLLAPSED", value: a!richTextItem( text: fv!index, color: "SECONDARY", size: "MEDIUM_PLUS", style: "STRONG" ), align: "CENTER" ), style: "STANDARD" ), width: "EXTRA_NARROW" ), a!columnLayout( contents: a!sideBySideLayout( items: { a!sideBySideItem(item: fv!item, width: "MINIMIZE"), a!sideBySideItem( item: a!buttonArrayLayout( buttons: a!buttonWidget( icon: "refresh", tooltip: "You may need to refresh more than once", saveInto: [INSERT QUERY HERE], style: "NORMAL" ), marginBelow: "NONE" ), width: "MINIMIZE", showWhen: fv!index = 3 ) }, alignVertical: "MIDDLE" ) ) }, alignVertical: "MIDDLE" ) } ) }, width: "MEDIUM" )
2. If there is no subsequent form to activity chain the user to and the signature is the last step in the workflow, one option is to not provide any Appian buttons at all then when the DocuSign Connect Webhook calls the Appian Web API, start a process model that messages the task form to hit an exception flow that ends the task and process model.
DocuSign has API rules for their web services and the most relevant rule is that apps are limited to one GET status request per unique envelope per 15 minutes. This means that process models should NOT have a node (and SAIL interfaces should not have a local variable) that checks the envelope status in DocuSign directly or regularly.
Instead, set up DocuSign Connect to call an Appian Web API when a relevant change to an envelope (such as it being signed) has occurred and start an Appian process model to begin post-processing (write the status to the database and download the document) at this time.
Since DocuSign Connect supports Basic Authentication but neither the username or password can be null, an Appian basic user account with a username and password must be created from DocuSign Connect to use when calling the Appian Web API
The webhook body is JSON formatted and can be accessed with standard dot notation or the index() function. You may extract the status and the envelopeId from the body as shown here:
a!localVariables( local!requestBody:a!fromJson( http!request.body), local!envelopeDetails: a!map( event: index(local!requestBody, "event", {}), envelopeId: index( index(local!requestBody, "data", {}), "envelopeId", {} ), ), a!startProcess( processModel: cons!VT_PM_RECEIVE_ENVELOPE_STATUS, processParameters: { envelopeDetails: local!envelopeDetails, requestBody:local!requestBody}, onSuccess: a!httpResponse( statusCode: 200, headers: {}, body: "Appian has received the envelope status and saved it." ), onError: a!httpResponse( statusCode: 302, headers: { a!httpHeader( "referer", http!request.queryParameters.referer ) }, ) ) )
The webhook body contains XML information about the envelope event, including the document ID, envelope ID, and envelope status. To be memory efficient, extract these values directly in the Web API expression rather than saving the XML into process variables.
While the XML can be configured to include the base64 representation of the signed document, DO NOT send or extract this value and save it into an Appian local variable or process variable since this will have negative impacts to the Appian memory profile. Instead, kick off a process model that downloads the document using the Appian OOTB operation to Download Document from Envelope which creates an Appian document in a specified folder, skipping the need to handle the base64 representation in variables.
If extracting dateTimes out of the XML, make sure to account for the time zone of the value since these can come in the signer’s time zone depending on the setup.
a!localVariables( /*Have to remove namespaces from the XML to be able to use xpath snippet easily */ local!cleanXML: substitute( substitute( ri!docusignXML, " 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""", "" ), " xsi:nil=""true"" ", "" ), /* Apply the xpath that you're looking for based on the values provided. Always returns the first one. */ 'type!{urn:com:appian:types:APP}APP_H_DocuSignEventNotification'( docusignDocumentId: xpathsnippet( local!cleanXML, "/DocuSignEnvelopeInformation/EnvelopeStatus/DocumentStatuses/DocumentStatus/ID/text()" ), envelopeId: xpathsnippet( local!cleanXML, "/DocuSignEnvelopeInformation/EnvelopeStatus/EnvelopeID/text()" ), docusignStatus: lower( xpathsnippet( local!cleanXML, "/DocuSignEnvelopeInformation/EnvelopeStatus/Status/text()" ) ) ) )
The xmltojson() plugin function can also be used in conjunction with a!fromJson() to turn the XML into an Appian dictionary that can be read using the index() function.
After the XML has been parsed and the status has been verified as “completed”, use the Appian OOTB operation to Download Document from Envelope and utilize the document as needed within the Appian application.
DocuSign integration keys cannot be created directly in production. The key is instead promoted from the demo environment for the customer account to the production environment for the customer account. This includes an automated API review in the demo environment for which a selected day of at least 20 API calls are reviewed to ensure no API violations. Plan for this process in the application deployment guide.
DocuSign's Go-Live Steps
The DocuSign Connect setup is done manually for lower environments and production. Plan for these manual steps in the application deployment guide.
DocuSign Connect