Verify Webhook Signature

Overview

Sunbit provides an option to verify webhook requests sent to your endpoints. Every webhook request will include a signature in the request’s header. You can use this signature in your code to make sure the request was actually sent by Sunbit.

Before you start, you will need your webhook’s signature. You can find it on the Sunbit developer portal under the Webhook section, the signature will be automatically generated when you set your Webhook endpoint.

Webhooks contain a header called “Sunbit-Signature” with the timestamp and a signature. The timestamp is prefixed by t= and the signature is prefixed by a scheme. Schemes start with v, followed by an integer. Currently, the only valid signature scheme is v1. Sunbit generates signatures using HMAC with SHA2-256.

Header example:


Sunbit-Signature: t=1565220904,v1=20c75c1180c701ee8a796e81507cfd5c932fc17cf63a4a55566fd38da3a2d3d2

Verify Webhook signature

Step 1: Extract the timestamp and signatures from the header

Split the header, using the , character as the separator, to get a list of elements. Split each element using the = character as the separator, to get a prefix and value pair. The value t represents the timestamp (in seconds), and v1 represents the signature.

Step 2: Prepare the signed_payload string

The signed_payload string is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The actual JSON payload (i.e., the request body)

Step 3: Determine the expected signature

Use the 3 components from Step 2 along with the Sunbit Signature to compute an HMAC with the SHA256 hash function. Depending on the language that you are using this will look something like the following:


const secret = 'sunbit webhook signature'; // Подпись вебхука из Sunbit Developers Portal
const timestamp = sunbitSignatureHeader.substring(sunbitSignatureHeader.indexOf('t=') + 2, sunbitSignatureHeader.indexOf(','));
const payload = timestamp + '.' + requestBody;
const expectedSignature = hmacSHA256(payload, secret);
const signature = sunbitSignatureHeader.substring(sunbitSignatureHeader.indexOf('v1=') + 3);

if (signature !== expectedSignature) {
  throw new Error('Invalid signature');
}

Step 4: Compare the signatures

Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance (recommended – up to 5 Min)

Examples


const crypto = require('crypto');

// Constants
const headerRegex = /t=(?\d{10}),v1=(?[a-f0-9]+)/;
const secret = 'DwS3QStMkgKziZxd9NXcvqFkxP4JNA3i'; //Header from request: sunbit-signature

//Header from request (example)
const header = 't=1643444288,v1=e1bfa98d067faeea521387c8917b71c96e32e1f9028a3b0b2167c4c7408cdacb'; 

//Read header using Regex
const found = header.match(headerRegex); 

//Extract the timestamp
const timestamp = found.groups.timestamp; 

//Construct payload from webhook body
const payload = timestamp + "." + JSON.stringify({ 
  "eventType": "MERCHANT_CREATED", 
  "payload": { 
    "location": "Merchant location", 
    "url": "merchant/application/url", 
    "statusReason": "NONE" 
  }
});

//Create HMAC from secret and payload
const hmac = crypto.createHmac('sha256', secret);
const data = hmac.update(payload, 'utf8');
const gen_hmac = data.digest('hex'); 

//Check for validity
if (gen_hmac === found.groups.signature) { 
  console.log('Valid signature');
} else { 
  console.log('Invalid signature');
}

//Printing the output on the console
console.log("hmac : " + gen_hmac);