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