Integrating with DocuSign

Key DocuSign Concepts

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

Envelope

An envelope can contain one or more documents, the fields in the document, recipient info, delivery progress, sender information, security and more.

Document

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.

Signature Seal

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.

Connectivity from Appian to DocuSign

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.

1. Service Account Integration (JWT Grant)

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.

DocuSign Setup Steps for Demo Environment

  1. Login to https://demo.docusign.net/ with Admin privileges.
  2. Navigate to the Settings tab.
  3. Click on Apps and Keys (under Integrations in the left navigation).
  4. Copy the User ID, API Account ID, and Account’s Base URI to a location where they’ll be easily copied.
  5. Click Add App / Integration Key.
  6. Add an appropriate App Name, ensuring you keep in mind the number of environments you will be connecting.
  7. Copy the Integration Key to the same location where the information in Step 4 was stored.
  8. Under Authentication, select Yes for the question on whether your application can securely store a client secret. (Responding No to this question also works for JWT grant as this method uses the RSA private key as opposed to the client secret key used in the Authorization Code grant method).
  9. Under Service Integration, click Generate RSA and copy the private key to the same location where the information in Step 4 was stored.
  10. Click SAVE.
  11. Follow the steps required to obtain individual consent for your application to act on a user’s behalf.

Appian Setup Steps

The easiest way to interact with DocuSign in Appian is through the use of a Connected System.

  1. Create a Connected System using proper naming conventions.
  2. Select DocuSign.
  3. Add an appropriate name and description.
  4. Under Authentication, select JWT Grant.
  5. Paste the Instance URL (Account’s Base URI), the API Account ID, API Username, Private RSA Key, and Integration Key.
    For the private key, the copied text needs to include “ -----BEGIN RSA PRIVATE KEY----- ” at the beginning and “ ----- END RSA PRIVATE KEY----- ” at the end.
  6. Click TEST CONNECTION.
  7. When you get a Connection successful alert, click CREATE.

2. Per User Authorization (Method 1) - Authorization Code Grant

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. 

DocuSign Setup Steps for Demo Environment

  1. Follow steps 1-7 from the Basic Authentication section above.
  2. Under Integration Key, select Authorization Code Grant.
  3. Add a Secret Key and copy the value.
  4. Add the [APPIAN_URL]/suite/oauth/callback URL to the Redirect URIs list.
  5. Click SAVE.

Appian Setup Steps

The easiest way to interact with DocuSign in Appian is through the use of a Connected System.

  1. Follow steps 1-3 from the JWT Grant Authentication section above.
  2. Under Authentication, select Authorization Code Grant.
  3. Paste the Instance URL (Account’s Base URI), the API Account ID, Integration Key, Secret Key into the Appian setup page then select the DocuSign Environment.
  4. Click AUTHORIZE to authorize Appian to make calls on the logged in user’s behalf (you may need to enter your DocuSign credentials at this time).
  5. When you get a Connection successful alert, click CREATE.

3. Per User Authorization (Method 2) - JWT Grant with Organizational Consent to Act on Behalf of All Users

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.

Docusign Setup

  1. Update the account settings on DocuSign by following these steps.
  2. Follow the steps to implement JWT Grant from the DocuSign documentation.

Appian Setup

  1. Download JWT Plugin from the app market.
  2. Create a third party credential key via the Admin Console (Admin Console -> Third Party Credentials -> Create).
    1. Name: Required field, select a unique easy-to-use name.
    2. Key: Appian will auto generate it from the name.
    3. Description: Optional field, enter an appropriate description.
    4. Plug-ins List: Optional field, type “JWT” (should show up to select).
    5. Credentials: Optional field, add two fields with the following details.
    6. Enter appianPrivateKey as Field Name and value as the DocuSign Admin Account’s generated Private RSA Key (note: remember to copy the entire —begin RSA private key— to —end RSA private key). Check the Mask box to hide it from the front end.
    7. Enter appianPrivateKeyPassword as Field Name and leave the value empty.
    8. Test Connection: Optional, leave it blank and click “Save”.
  3. Generate Admin’s Access Token.
    1. Create an expression rule to generate the “Access Token.”
      1. Add a rule input for user ID as this rule will be used to generate JWT for the admin as well as the user to impersonate.
      2. Use function - createdocusignrsajwttoken() provided by the JWT Plug-in.
      3. Note that the plug-in requires you to follow the order of the parameters.
      4. Use guidance from DocuSign to generate the JWT
    2. Create an integration to generate the admin access token.
      1. Add a rule input for passing the JWT generated in Step 3a(i)
      2. Under Request Body select “Content Type” as Multipart and provide assertion which will the generated Admin JWT and grant_type as "urn:ietf:params:oauth:grant-type:jwt-bearer"
      3. For additional guidance, follow the DocuSign documentation
      4. Parse the access token from the response.
  4. Generate Sender’s Access Token
    1. The admin’s generated token will be used to retrieve the sender’s information (as long as they are part of the Organization in DocuSign)
    2. Create an integration to retrieve user information (DocuSign Documentation) using their email address registered with DocuSign and the admin access token generated in Step 3a(i).
      1. URL: The “Account ID” will be available in the DocuSign settings, recommended to create a constant to store the value. Note that the URL will vary depending on the environment.
      2. Query Parameters: Add “email” which will be the user’s email address.
      3. Headers: Add “Authorization” where the value will be (in expression mode) “Bearer” + the admin’s access token.
    3. Parse the “userId” from the generated response and use it in the rule created in Step 3(a) to retrieve the sender’s JWT.
    4. Use the integration created in Step 3(b) to generate the sender’s access token.
      1. The rule input value will be the sender’s generated JWT.
      2. Parse the access token from the response.
  5. Create integration to send envelope as the user via DocuSign
    1. With the sender’s access token, DocuSign integrations can be called to create and send envelopes, etc. Note that we cannot use the out of the box Docusign Connected System for any integrations that need a bearer token.
    2. In the Authorization Header for the integration, pass the sender’s generated access token.

DocuSign Integrations

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.

Implementing Integrations in Appian

Create Envelope

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"
              }
            }            
          }
        }
      }
    }
  }
)

Noteworthy Fields

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.

Signing Documents

Remote

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.

Embedded

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"
)

Noteworthy Fields

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.

Design Tips

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.

Receiving Envelope Status Updates

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.

Setting Up DocuSign Connect

Appian Setup

  1. Create an Appian group for APP DocuSign Service Accounts.
  2. Create an Appian Web API as a POST method.
  3. Sections below will help define the expression for this object.
  4. Give the Appian group viewer rights to the Web API.
  5. Create a Service Account in the Admin Console under Web API Authentication > OAuth 2.0 Clients > Create.
    Basic Authentication is possible to use in place of OAuth 2.0, but is not recommended due to poor security and expiring password. OAuth 2.0 is the industry-standard protocol for authorization, and should generally be used when possible.
  6. Copy the Client ID, Client Secret, and Token Request Endpoint to a secure location where they can be retrieved, as the secret will not be displayed again in the admin console.
  7.  Add the service account to the Appian group. Also ensure that the service group has at least ‘Initiator’ privileges to the process model called in your Appian Web API.

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

DocuSign Setup

  1. Login to the DocuSign with Admin privileges.
  2. Navigate to the Settings tab.
  3. Click on Connect (under Integrations in the left navigation).
  4. Select Add Configuration > Custom.
  5. Add the setup details including Web API URL.
  6. Choose the data format (Legacy vs REST V2.1). When the Legacy format is chosen, the message from Docusign is an older XML format whereas the REST v2.1 is a newer JSON format that is easier to process, and is the recommended option for Appian Integrations.
  7. Select the user envelopes that should trigger the webhook callback. The correct value will depend on the REST API authentication method and customer DocuSign setup.
  8. Select the events that should trigger the webhook callback.
  9. Check the box to include OAuth.
  10. Click ADD CONFIGURATION.
  11. Select the OAUTH 2.0 tab.
  12. Enter in the Client ID, Client Secret, and Authorization Server URL.
  13. Click SAVE.

Parsing the DocuSign Connect Request - (REST v2.1)

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
        )
      },
     
    )
  )
)

Parsing the DocuSign Connect XML (Legacy)

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.

Using XPath

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()"
      )
    )
  )
)

Using XML Parser Plugin

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.

Download Signed Document

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.

Promoting DocuSign Configurations to Production

DocuSign Integration Key

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

DocuSign Connect

The DocuSign Connect setup is done manually for lower environments and production. Plan for these manual steps in the application deployment guide.

DocuSign Connect