Sample Code
webhook
For the .NET source code click here.
<?php
/* ------------------------ WEBHOOK Endpoint -------------------------------- */
//Required headers
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: POST");
header("Access-Control-Max-Age: 3600");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
//get MyFatoorah-Signature from request headers
$request_headers = apache_request_headers();
$MyFatoorah_Signature = $request_headers['MyFatoorah-Signature'];
//Webhook secret
//You can get Webhook Secret Key as described in https://myfatoorah.readme.io/docs/webhook-information
$secret = 'copy here the Webhook Secret Key generated in MyFatoorah vendor account';
//Get webhook body content
$body = (file_get_contents("php://input"));
$data = json_decode($body, true);
//Log data to check the result
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - ' . $body, 3, './webhook.log');
//Validate the signature
validateSignature($data, $secret, $MyFatoorah_Signature);
//Call $data['Event'] function
if (empty($data['Event'])) {
exit();
}
$data['Event']($data['Data']);
/* ------------------------ Functions --------------------------------------- */
/*
* validateSignature function
*/
function validateSignature($body, $secret, $MyFatoorah_Signature) {
if ($body['Event'] == 'RefundStatusChanged') {
unset($body['Data']['GatewayReference']);
}
if ($body['Event'] == 'SupplierStatusChanged') {
unset($body['Data']['KycFeedback']);
}
$data = $body['Data'];
//1- Order all data properties in alphabetic and case insensitive.
uksort($data, 'strcasecmp');
//2- Create one string from the data after ordering it to be like that key=value,key2=value2 ...
$orderedData = implode(',',
array_map(function ($v, $k) {
return sprintf("%s=%s", $k, $v);
},
$data,
array_keys($data)
));
//4- Encrypt the string using HMAC SHA-256 with the secret key from the portal in binary mode.
//Generate hash string
$result = hash_hmac('sha256', $orderedData, $secret, true);
//5- Encode the result from the previous point with base64.
$hash = base64_encode($result);
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Generated Signature - ' . $hash, 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - MyFatoorah-Signature - ' . $MyFatoorah_Signature, 3, './webhook.log');
//6- Compare the signature header with the encrypted hash string. If they are equal, then the request is valid and from the MyFatoorah side.
if ($MyFatoorah_Signature === $hash) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Signature is valid ', 3, './webhook.log');
return true;
} else {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Signature is not valid ', 3, './webhook.log');
exit;
}
}
//------------------------------------------------------------------------------
/*
* TransactionsStatusChanged function
*/
function TransactionsStatusChanged($data) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Inside ' . __FUNCTION__ . ' function', 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Transaction Status is ' . $data['TransactionStatus'], 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - You should update order status with Transactions Status Changed webhook output ', 3, './webhook.log');
}
//------------------------------------------------------------------------------
/*
* RefundStatusChanged function
*/
function RefundStatusChanged($data) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Inside ' . __FUNCTION__ . ' function', 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - You should update order with Refund Status Changed webhook output ', 3, './webhook.log');
}
//------------------------------------------------------------------------------
/*
* RecurringStatusChanged function
*/
function RecurringStatusChanged($data) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Inside ' . __FUNCTION__ . ' function', 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - You should update order with Recurring Status Changed webhook output ', 3, './webhook.log');
}
//------------------------------------------------------------------------------
/*
* BalanceTransferred function
*/
function BalanceTransferred($data) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Inside ' . __FUNCTION__ . ' function', 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - You should update your system with Balance Transferred webhook output ', 3, './webhook.log');
}
//------------------------------------------------------------------------------
/*
* SupplierStatusChanged function
*/
function SupplierStatusChanged($data) {
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - Inside ' . __FUNCTION__ . ' function', 3, './webhook.log');
error_log(PHP_EOL . date('d.m.Y h:i:s') . ' - You should update your system with Supplier Status Changed webhook output ', 3, './webhook.log');
}
/* -------------------------------------------------------------------------- */
using System.Web.Http;
using System.Web;
using System.IO;
using Newtonsoft.Json;
using MF.Models.DTO.Webhook;
using System;
using System.Linq;
using MF.Core.Enums;
using System.Collections.Generic;
namespace MF.Api.Controllers
{
public class WebhookController : BaseApiController
{
[HttpPost]
[Route("Webhook/Index")]
public IHttpActionResult Index()
{
try
{
var json = new StreamReader(HttpContext.Current.Request.InputStream).ReadToEndAsync().Result;
var signatureHeader = HttpContext.Current.Request.Headers["MyFatoorah-Signature"];
string secretKey = "", headerSignature = "";
bool isValidSignature = true;
if (signatureHeader != null) //Check If Enabled Secret Key and If The header has request
{
isValidSignature = false;
headerSignature = signatureHeader.ToString();
secretKey = "/Xp+v8r2dDmNlOTgFyuSRoASudhBm04AzJ6891UWz4k="; //From Your Portal.
}
var model = JsonConvert.DeserializeObject<GenericWebhookModel<object>>(json);
switch (model.EventType)
{
case WebhookEvents.TrnasactionsStatusChanged:
var transactionModel = JsonConvert.DeserializeObject<GenericWebhookModel<WebhookTransactionStatus>>(json);
if (!isValidSignature)
{
isValidSignature = CheckMyFatoorahSignature(transactionModel, secretKey, headerSignature);
if (!isValidSignature) return BadRequest("Invalid Signature");
}
//Do Some Work
break;
case WebhookEvents.RefundStatusChanged:
var refundModel = JsonConvert.DeserializeObject<GenericWebhookModel<WebhookRefund>>(json);
if (!isValidSignature)
{
isValidSignature = CheckMyFatoorahSignature(refundModel, secretKey, headerSignature);
if (!isValidSignature) return BadRequest("Invalid Signature");
}
//Do Some Work
break;
case WebhookEvents.BalanceTransferred:
var depositModel = JsonConvert.DeserializeObject<GenericWebhookModel<WebhookDeposit>>(json);
if (!isValidSignature)
{
isValidSignature = CheckMyFatoorahSignature(depositModel, secretKey, headerSignature);
if (!isValidSignature) return BadRequest("Invalid Signature");
}
//Do Some Work
break;
case WebhookEvents.SupplierStatusChanged:
var supplierModel = JsonConvert.DeserializeObject<GenericWebhookModel<WebhookSupplierStatus>>(json);
if (!isValidSignature)
{
isValidSignature = CheckMyFatoorahSignature(supplierModel, secretKey, headerSignature);
if (!isValidSignature) return BadRequest("Invalid Signature");
}
//Do Some Work
break;
}
return Ok();
}
catch (Exception e)
{
return BadRequest(e.Message);
}
}
public bool CheckMyFatoorahSignature<T>(GenericWebhookModel<T> model, string secretKey, string headerSignature)
{
//***Generate The Signature*** :
//1- Order all properties alphabetic
//2-Encrypt the data with the secret key
//3-Compare the signature
var properties = typeof(T).GetProperties().Select(p => p.Name).OrderBy(p => p).ToList();
var type = model.Data.GetType();
var parameters = new List<ItemTxt>();
for (int i = 0; i < properties.Count; i++)
{
var value = type.GetProperty(properties[i]).GetValue(model.Data);
value = value == null ? "" : value.ToString();
parameters.Add(new ItemTxt { Text = properties[i], Value = value.ToString() });
}
var signature = Sign(parameters, secretKey);
return signature == headerSignature;
}
private static string Sign(List<ItemTxt> paramsArray, string secretKey)
{
var dataToSign = paramsArray.Select(p => p.Text + "=" + p.Value).ToList();
var data = string.Join(",", dataToSign);
var encoding = new UTF8Encoding();
var keyByte = encoding.GetBytes(secretKey);
var hmacsha256 = new HMACSHA256(keyByte);
var messageBytes = encoding.GetBytes(data);
return Convert.ToBase64String(hmacsha256.ComputeHash(messageBytes));
}
}
public class ItemTxt
{
public string Value { get; set; }
public string Text { get; set; }
}
}
function validateSignature(bodyString, secret, myFatoorahSignature) {
var json = JSON.parse(bodyString);
if (json['Event'] === 'RefundStatusChanged') {
delete json['Data']['GatewayReference'];
}
var unOrderedArray = json['Data'];
//1- Order all data properties in alphabetic and case insensitive.
const orderedArray = Object.keys(unOrderedArray).sort((a, b) => a.localeCompare(b));
//2- Create one string from the data after ordering its key to be like that key=value,key2=value2 ...
let orderedString = "";
orderedArray.forEach(key => {
unOrderedArray[key] = (unOrderedArray[key]) ? unOrderedArray[key] : '';
orderedString += `${key}=${unOrderedArray[key]},`;
});
orderedString = orderedString.slice(0, -1);
//4- Encrypt the string using HMAC SHA-256 with the secret key from the portal in binary mode.
const crypto = require('crypto');
let result = crypto.createHmac("sha256", secret).update(orderedString).digest();
//5- Encode the result from the previous point with base64.
let hash = result.toString('base64');
//6- Compare the signature header with the encrypted hash string. If they are equal, then the request is valid and from the MyFatoorah side.
console.log('hash = ' + hash);
console.log('MyFatoorah_Signature = ' + myFatoorahSignature);
if(hash === myFatoorahSignature){
console.log('valid');
}else{
console.log('error');
}
}
import hashlib, base64, hmac
# Validate webhood signature function.
def validate_signature(response, secret, MyFatoorah_Signature):
# removing "GatewayReference" from the data string
bodyString = response.json()["Data"]
if "GatewayReference" in bodyString.keys():
del bodyString["GatewayReference"]
if "KycFeedback" in bodyString.keys():
del bodyString["KycFeedback"]
# sorting the string and removing null values
required_string = ""
for key in sorted(bodyString, key=str.lower):
if bodyString[key] in [None, "null"]:
bodyString[key] = ""
x = "{key}={value},".format(key=key, value=bodyString[key])
required_string = required_string + x
required_string = required_string.rstrip(required_string[-1])
print(required_string)
# Encode the secret key and ordered data with UTF-8
message_encoded = required_string.encode('utf8')
secret_encoded = secret.encode('utf8')
#Encrypt the string using HMAC SHA-256 with the secret key from the portal in binary mode.
signature = base64.b64encode(hmac.new(secret_encoded, message_encoded, digestmod=hashlib.sha256).digest())
signature = signature.decode('utf8')
if signature == MyFatoorah_Signature:
result = "Valid"
else:
result = "Invalid"
print(result)
return result
Updated about 1 year ago