Webhooks Authentication
Duda can sign requests using the HMAC-SHA256 algorithm to allow your app to verify the authenticity of the sender (Duda) and of the payload.
This feature is available only for managed accountsIn order to access this feature, schedule a time to speak with a member of our Partner Team about upgrading your account to a Custom plan.
Comparing signatures
The signature value is calculated as follows:
- You should concatenate the timestamp (passed in the
x-duda-signature-timestampheader) with the body message, separated by a period.const message = timestamp + "." + bodyString. - The secret key given to you by Duda should be base64 decoded.
- Generate an HMAC Hash using the
sha256algorithm from the variables above. Ensure the output is in binary (for example, PHP defaults to hex). - Base64 encode the resulting HMAC Hash.
- Compare the encoded HMAC Hash to the signature you received in the
x-duda-signatureheader in the request. If they don't match, throw an error and stop all execution.
An example with data
The request body is {'key1':'world','key2':'world'} the timestamp (x-duda-signature-timestamp header) is 1570350275357 and our secret key is mysecretsecret.
Calculating base64(hmac-sha256(secret-key, timestamp + "." + message)) results in +DCfT1wIMUiaZnlZB4u59/d5wkXKA89lv67Ov66vnyc= which is the value we will find in x-duda-signature header of the signed request.
Code examples
String getHMAC(String timestamp, String message) {
String secretKey = ...;
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
String input = timestamp + "." + message;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(input.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(rawHmac));
}
String body = request.getBody();
String timestamp = request.getHeader("x-duda-signature-timestamp");
String hmac = getHMAC(timestamp, body);
String signatureHeader = request.getHeader("x-duda-signature");
if (MessageDigest.isEqual(hmac.getBytes(), signatureHeader.getBytes())) {
System.out.println("Signature verified, proceed!");
}const crypto = require('crypto');
const sig = req.headers["x-duda-signature"];
const time = req.headers["x-duda-signature-timestamp"];
const body = JSON.stringify(req.body);
const key = Buffer.from(secretKey, "base64");
const check = crypto.createHmac("sha256", key)
.update(`${time}.${body}`)
.digest("base64");
const sigBuffer = Buffer.from(sig);
const checkBuffer = Buffer.from(check);
if (sigBuffer.length === checkBuffer.length && crypto.timingSafeEqual(sigBuffer, checkBuffer)) {
console.log("Signature verified, proceed!");
}<?php
if (strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0) {
throw new Exception('Request method must be POST!');
}
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
if (strcasecmp($contentType, 'application/json') != 0) {
throw new Exception('Content type must be: application/json');
}
$body = trim(file_get_contents("php://input"));
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
$sigToVerify = $headers['x-duda-signature'];
$timestamp = $headers['x-duda-signature-timestamp'];
$message = $timestamp . '.' . $body;
$secret = base64_decode('<B64_SECRET_GOES_HERE>');
$hmac = base64_encode(hash_hmac('sha256', $message, $secret, true));
if (hash_equals($sigToVerify, $hmac)) {
echo 'Signature verified, proceed!';
}Updated 7 days ago