Webhook Security

eDRV Webhook Signatures

eDRV can optionally sign webhook events it sends to your endpoints by including a signature in each event’s edrv-signature header. This allows you to verify that the events were sent by eDRV, not by a third party. See the code sample below on how you can verify eDRV signatures.

Replay Attacks Prevention

To prevent replay attacks, signature verification includes a timestamp that the application developer can use to consider a webhook as expired after a certain period of time, for example, 3 minutes. You can adjust this expiry period within your code based on your security requirements.

Webhook Endpoint Secret

Before you can verify signatures, you need to retrieve your endpoint’s secret from your Dashboard’s Webhooks settings. Select an endpoint that you want to obtain the secret for, then click the Click to reveal button. An endpoint may subscribe to multiple eDRV Webhook Events.

eDRV generates a unique secret key for each endpoint. If you use multiple endpoints, you must obtain a secret for each one you want to verify signatures on. After this setup, eDRV starts to sign each webhook it sends to the endpoint.

Locate your Webhook Endpoint Secret

From Admin Panel, Go to Webhook Tab , click on Endpoint Edit Icon > Click on eye icon for the secret.

Verifying Signatures

We sign all webhooks with a SHA256 signature and include them in the request's edrv-signature header. You don't have to validate the signature, but you should and we strongly recommend that you do. The edrv-signature header contains a timestamp and a signature. The timestamp is prefixed by t=, and the signature is prefixed by a scheme. Schemes start with v, followed by a version. Currently, the valid signature scheme is v1.

Extract the timestamp and signatures from the header

In order to extract the timestamp and signature from the header, use the , (comma) character as the separator, to get a list of elements. Then split each element, using the = (equals to) character as the separator, to get a prefix and value pair.

📘

The prefix t value corresponds to the timestamp, and v1 corresponds to the signature.

Validate the timestamp and signature

In order to validate the timestamp, extract the timestamp element t from the edrv-signature and check it against the current time it should be within the tolerance limit set by you (ex: 3 mins or 5 mins). It is recommended to use Network Time Protocol (NTP) to ensure that your server clock is synchronised with the eDRV system. You can also perform an additional check by matching the timestamp t from the header with the createdAt timestamp in the decoded signature element v1.

In order to validate the signature, generate a SHA256 signature using the payload and your endpoint secret. Compare your signature to the signature in the edrv-signature header element v1. If the signatures match, the payload is genuine.

📘

Escaped Unicode

Please note that we generate the signature using an escaped unicode version of the payload, with lowercase hex digits. If you just calculate against the decoded bytes, you will end up with a different signature. For example, the string ëä should be escaped to \u00EB\u00E4.

const crypto = require("crypto")
const moment = require("moment")
const signature = req.headers["edrv-signature"];
// t=1681983610864,v1=b5b64fb3b799f806ce9182c67d218ae7c6e0e8e1e9fe3e91d2f367b9aa1bdda9

const secret = "";
const TOLERANCE_LIMIT =  120000 // in milliseconds
const responseBody = JSON.stringify({
  // Webhook response payload
})

if (!signature) {
  console.error(`Could not find "eDRV-Signature" in headers.`);
} else {
  const elements = signature.split(",");
  
  // Extract and match timestamp
  const timestamp = elements[0].split("=")[1];
  const timeDifference = moment().valueOf() - parseInt(timestamp)
  if(!timeDifference || TOLERANCE_LIMIT < timeDifference){
    throw new Error("Request Timestamp is not within the tolerance limit")
  }

  // Extract and match the signature
  const signatureHash = elements[1].split("=")[1];
  const expectedHash = crypto
    .createHmac("sha256", secret)
    .update(responseBody)
    .digest("hex");
  if (signatureHash !== expectedHash) {
    throw new Error("Request signature validation failed");
  } 
  
  console.log("Valid Request")
}