Webhooks

Security Overview

Snapdocs Webhooks allow an integration to follow along with all events happening on the closings processing today for your company. Because we are posting to endpoints that are out of Snapdocs control, we have prioritized security through the following primary features. Snapdocs recommends leveraging a combination of these features be used to secure the integration, however we cannot enforce them as they are changes on the side of the integrator in many cases. Securing the integration requires a secure partner client.


FeatureQuick Summary
No P.I.I.Snapdocs event messages contain event names and identifiers for transactions or documents. All PII requires a follow on authenticated call.
IP WhitelistingSnapdocs broadcasts from a single set of IP addresses for all of our outbound traffic to allow integrators to lock down the endpoint to our broadcast.
Authenticated EndpointsSnapdocs now allows OAuth2 authenticated calls, so we can post with a Bearer token if your endpoint is a client_credentials protected endpoint
HMAC SignatureSnapdocs webhooks contain an HMAC key that can be validated against a signature we create, unique to every message. Only the creator and your integration can validate the signature using the HMAC key.
HTTPS / TLSSnapdocs requires an Https endpoint for our webhook creation.

Besides these features, we also recommend your integration endpoints / listener is built with appropriate rate limiting, TLS 1.3+, SSL, and as many of the above features as you can. We also encourage the regular rotation of the HMAC keys for the signature feature, by creating a new subscription to replace the old. Please rotate your HMAC keys following any security incident.

Handling of Personally Identifiable Information (PII)

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.

IP Whitelisting

To enhance security, Snapdocs supports IP whitelisting for both test and production environments. Customers may restrict access to their endpoints by specifying a list of approved IP addresses. Your Snapdocs representative can provide the current list of IP addresses used by Snapdocs services for both test and production environments. We recommend updating your firewall rules to allow only these IPs, ensuring that only authorized Snapdocs traffic can access your systems. Snapdocs recommends that your listener is built with whitelisting, rate limiting, TLS 1.3+, SSL, and the signature validation outlined below.

Authenticated Endpoints

Snapdocs Create a Subscriptionendpoint now takes in optional OAuth2 details such as token_url, client_id, client_secret for a client_credentials OAuth2 flow or Basic Auth. Our webhooks will get a token and send an authenticated call to your endpoint for all events.

Security Signature

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

    }
}
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;

public class SignatureVerification
{
    public static void VerifyRequestSignature(HttpRequest request, string hmacKey)
    {
        // Read headers
        string timestamp = request.Headers["X-Authorization-Timestamp"];
        string requestSignature = request.Headers["X-Authorization-Signature"];

        // Read request body
        string requestBody;
        using (var reader = new StreamReader(request.Body, Encoding.UTF8))
        {
            requestBody = reader.ReadToEnd();
        }

        // Concatenate timestamp and request body
        string data = timestamp + requestBody;

        // Compute HMAC-SHA256 digest
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(hmacKey)))
        {
            byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));

            // Encode the hash in Base64
            string computedSignature = Convert.ToBase64String(hashBytes);

            if (computedSignature == requestSignature)
            {
                // Normal process
                Console.WriteLine("Signature is valid. Proceed with the process.");
            }
            else
            {
                // Did not come from Snapdocs APIs
                Console.WriteLine("Invalid signature. Request did not come from Snapdocs APIs.");
            }
        }
    }
}