Security Overview

Snapdocs Connect events that are broadcast will never contain PII or sensitive information. We broadcast various identifiers, statuses, and document statuses. If subsequent actions require more information or API calls, all retrieval of pertinent information requires authenticated API calls.

Snapdocs recommends that your listener is built with whitelisting, rate limiting, TLS 1.3+, SSL, and the signature validation outlined below.

Snapdocs Connect supports the rotation of HMAC keys for signature checking by creating new subscriptions and deleting the existing. We recommend rotating keys following any security incident in the integration, and at a regular cadence.

Snapdocs strongly recommends securing the integration connection on the client listening side, because many of our security mechanisms rely on a secure partner client.

Signature Security feature

Snapdocs Connect has a secure mechanism into our webhook broadcasts, using an HMAC hash using the secret value returned at subscription creation. Our webhooks broadcast a secure timestamp and a hash using the HMAC secret. We strongly recommend the clients validate the timestamp and signature to ensure the data is sent by Snapdocs Connect and not tampered with in transit.

The Snapdocs Connect message will include the following HTTP headers

  • X-Authorization-Digest, the algorithm that Snapdocs Connect uses to generate the signature, “HMACSHA256”
  • X-Authorization-Timestamp, the ISO-8601 format of timestamp, for example “2021-12-17T19:08:59Z”
  • X-Authorization-Signature, the base64 encoded HMAC signature to compare.

Below are steps that clients usually take to verify the signature of the payload:

  1. Extract the digest and timestamp from the header
  2. Calculate the payload signature using the digest algorithm against the timestamp, in the above example it’s HMACSHA256
  3. Extract the request signature from the header
  4. Compare the request signature with the calculated signature
  5. If they match, process the closing event per your integration
  6. If they do not match, discard the message which didn’t originate from Snapdocs

Below is an example Ruby code snippet to demonstrate the computing signature at client side:

require 'openssl'
require 'base64'

timestamp = request.headers["X-Authorization-Timestamp"]
request_signature = request.headers["X-Authorization-Signature"]
data = request.body.read

hash_bytes = OpenSSL::HMAC.digest('sha256', hmac_key, timestamp.concat(data))
computed_signature = Base64.strict_encode64(hash_bytes)
if computed_signature == request_signature
   # normal process
else
   # did not come from SD
import hmac
import hashlib
import base64

request_signature = request.headers["X-Authorization-Signature"]
timestamp_bytes = bytes(request.headers["X-Authorization-Timestamp"], 'utf-8')
hmac_key_bytes = bytes(hmac_key, 'utf-8')
data_bytes = request.content

hash_bytes = hmac.new(hmac_key_bytes, timestamp_bytes + data_bytes, hashlib.sha256).digest()
computed_signature = base64.b64encode(hash_bytes).decode("utf-8")
if computed_signature == request_signature:
   # normal process
else:
   # did not come from SD
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class WebhookSecurityExample {

    public static void verifySignature(final String hmac_key, final String request_signature, final String timestamp, final String request_body) {
        try {

            final Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            final SecretKeySpec secret_key = new SecretKeySpec(hmac_key.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);

            final String data = timestamp + request_body;
            final String encoded_signature = Base64.encodeBase64String(sha256_HMAC.doFinal(data.getBytes()));
            final String computed_signature = StringUtils.newStringUsAscii(Base64.decodeBase64(encoded_signature));

            if (computed_signature.equals(request_signature)) {
                // normal process
            } else {
                // did not come from SD
            } catch (Exception e) {
                System.out.println("Error");
            }
        }

    }
}