Ecom sign the events it sends to your endpoints by including a signature in each event’s " X-Webhook-Signature" header. This allows you to verify that the events were sent by Ecom, not by a third party.
Ecom generates signatures using a hash-based message authentication code (HMAC) with SHA-256. To prevent downgrade attacks, you should order the data model properties alphabetic with its values then encrypt them by the secret key then compare the generated signature with Webhook Signature to make sure that this request is from our side.
- Order all data properties in alphabetic and case insensitive.
- Create one string from the data after ordering it to be like that key=value,key2=value2 ...
- Encode the secret key and ordered data with UTF-8.
- Encrypt the string using HMAC SHA-256 with the secret key from the portal in API Credential tab.
- Compare the signature header with the encrypted hash string. If they are equal, then the request is valid and from the Ecom side.
If the value of any property is null, remove it before apply the sorting , this step provided in the below examples
Examples :
class WebhookService {
generateHmacSignature(payload, webhookSecretKey) {
const payloadString = this.sortObjectKeys(payload);
const hmac = createHmac('sha256', webhookSecretKey);
hmac.update(payloadString);
return hmac.digest('hex');
}
sortObjectKeys(obj) {
const entries = Object.entries(obj);
return entries.filter(([_, value]) => value !== null) // Remove null values
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
.map(([key, value]) => `${key}=${value}`)
.join('&');
}
}
// Usage
const webhookService = new WebhookService();
const receivedFromEcom = {
"timestamp": "2025-02-04T12:12:12Z",
"eventType": "TRANSACTION_STATUS_CHANGED",
"data": {
"ecomId": "4011738671117962347",
"paymentStatus": "INITIATED",
"product": "E_API",
"amount": "100.5050",
"currency": "KWD",
"ecomReference": "E_A_25A00189",
"productPaymentToken": "353fa038-393a-4d44-b7d2-31e2f5c6ca8d",
"merchantReference": "string",
"customerFullName": "Ali",
"customerPhoneCode": "+965",
"customerPhoneNumber": "66778899",
"paymentMethod": "KNET"
}
};
const webhookSecretKey = 'my_secret_key';
// generate signature of data object received from ecom
const signature = webhookService.generateHmacSignature(receivedFromEcom.data, webhookSecretKey);
// compare against signature u got from x-webhook-signature
// if it match then everything is ok
<?php
class WebhookService {
public function generateHmacSignature($payload, $webhookSecretKey) {
$payloadString = $this->sortObjectKeys($payload);
return hash_hmac('sha256', $payloadString, $webhookSecretKey);
}
private function sortObjectKeys($obj) {
$filtered = array_filter($obj, fn($value) => $value !== null); // Remove nulls
ksort($filtered);
$entries = [];
foreach ($filtered as $key => $value) {
$entries[] = $key . '=' . (is_array($value) ? json_encode($value) : $value);
}
return implode('&', $entries);
}
}
// Usage
$webhookService = new WebhookService();
$receivedFromEcom = [
"timestamp" => "2025-02-04T12:12:12Z",
"eventType" => "TRANSACTION_STATUS_CHANGED",
"data" => [
"ecomId" => "4011738671117962347",
"paymentStatus" => "INITIATED",
"product" => "E_API",
"amount" => "100.5050",
"currency" => "KWD",
"ecomReference" => "E_A_25A00189",
"productPaymentToken" => "353fa038-393a-4d44-b7d2-31e2f5c6ca8d",
"merchantReference" => "string",
"customerFullName" => "Ali",
"customerPhoneCode" => "+965",
"customerPhoneNumber" => "66778899",
"paymentMethod" => "KNET",
]
];
$webhookSecretKey = 'my_secret_key';
$signature = $webhookService->generateHmacSignature($receivedFromEcom["data"], $webhookSecretKey);
// compare against signature u got from x-webhook-signature
// if it match then everything is o
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
public class WebhookService
{
public string GenerateHmacSignature(Dictionary<string, object> payload, string webhookSecretKey)
{
string payloadString = SortObjectKeys(payload);
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSecretKey)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payloadString));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
private string SortObjectKeys(Dictionary<string, object> obj)
{
var sortedKeys = obj
.Where(entry => entry.Value != null) // Remove nulls
.OrderBy(entry => entry.Key);
return string.Join("&", sortedKeys.Select(entry => $"{entry.Key}={entry.Value}"));
}
}
// Usage
class Program
{
static void Main()
{
var webhookService = new WebhookService();
var receivedFromEcom = new Dictionary<string, object>
{
{"timestamp", "2025-02-04T12:12:12Z"},
{"eventType", "TRANSACTION_STATUS_CHANGED"},
{"data", new Dictionary<string, object>
{
{"ecomId", "4011738671117962347"},
{"paymentStatus", "INITIATED"},
{"product", "E_API"},
{"amount", "100.5050"},
{"currency", "KWD"},
{"ecomReference", "E_A_25A00189"},
{"productPaymentToken", "353fa038-393a-4d44-b7d2-31e2f5c6ca8d"},
{"merchantReference", "string"},
{"customerFullName", "Ali"},
{"customerPhoneCode", "+965"},
{"customerPhoneNumber", "66778899"},
{"paymentMethod", "KNET"}
}
}
};
string webhookSecretKey = "my_secret_key";
string signature = webhookService.GenerateHmacSignature(
(Dictionary<string, object>)receivedFromEcom["data"], webhookSecretKey);
// compare against signature u got from x-webhook-signature
// if it match then everything is o
}
}