Step 3: Verify Payout webhook (merchant implementation)

Ozow requires that each integrating merchant implements a payout verification POST webhook will be used for the following:

  • To verify a payout request
  • To provide Ozow with the AES decryption key which will be used to decrypt the payout destination account number.

📘

Note:

If Ozow fails to decrypt the account number with the decryption key provided, a payout cancelled notification will be send back to the merchant.

Webhook authentication

📘

Note:

At a minimum, the webhook should implement bearer/auth header type authentication. The service call will use the access token provided by the merchant where it is passed in the header as a AccessToken field to the webhook endpoint.

Post variables

PropertyTypeReq.Description
1.PayoutIdGuidYesA unique identifier that should be used to identify the payout.
2.SiteCodeString (50)YesThe merchant’s site code. [Please contact support for SiteCode - [email protected]]
3.AmountDecimal (9,2)YesThe payout amount.
4.MerchantReferenceString (20)YesThe merchant's reference for the payout.
5.CustomerBankReferenceString (20)YesThe reference that will appear on the merchant’s bank statement and can be used for recon purposes.
6.IsRtcboolYesWhether the payout should be processed as an RTC payout.
7.NotifyUrlString (150)NoThe URL that we should use to post the all payout notifications.
8.BankingDetailsObjectYesPayout destination banking details. Refer to table below.
9.HashCheckString (250)YesSHA512 hash used to ensure that certain fields in the message have not been altered after the hash was generated. Check the generate hash section below for more details on how to generate the hash.

BankDetails Object

PropertyTypeReq.Description
BankGroupIdGuidYesThe unique bank identifier.
AccountNumberString (32)YesThe bank account number the payment should be made to. The account number should be encrypted by the encryption method detailed above.
BranchCodeString (10)YesThe destination bank branch code.

Verify webhook hash check

These steps will be used to generate the hash check, which should be validated to ensure a legitimate webhook verification has been initiated from Ozow:

  1. Concatenate the post variables (excluding HashCheck) in the order they appear in the post variables table.
  2. Your API key will be appended to the concatenated string.
  3. Convert the concatenated string to lowercase.
  4. Generate a SHA512 hash of the lowercase concatenated string.

Verification Request Hash Calculation

using System.Security.Cryptography;
using System.Text;

GeneratePayoutVerificationRequestHash();

void GeneratePayoutVerificationRequestHash()
{    
  	var PayoutId = "00000000-0000-0000-0000-000000000000";
    var siteCode = "[YOUR SITE CODE]";
    var amount = 17.15;
    var MerchantReference = "123";
    var CustomerBankReference = "ABC123";    
    var IsRtc = false;
    var NotifyUrl = "https://requestcatcher.com/";
    var BankGroupId = "13999FA-3A32-4E3D-82F0-A1DF7E9E4F7B";
    var AccountNumber = "ff313a955ad9a8ddff32cb734d49fbcddd8eeb1e235009d59a801bc5af78270cfd";
    var BranchCode = "198765";
  	var apiKey = "[YOUR API KEY]";
  
    var inputString = string.Concat(PayoutId,
        SiteCode,
        Convert.ToInt32(Amount * 100),
        MerchantReference,
        CustomerBankReference,
        IsRtc,
        NotifyUrl,
        BankGroupId,
        AccountNumber,
        BranchCode,
        apiKey);

    var calculatedHashResult = GenerateRequestHashCheck(inputString);
    Console.WriteLine($"Hashcheck: {calculatedHashResult}");
 }

string GenerateRequestHashCheck(string inputString)
{
    var stringToHash = inputString.ToLower();
    Console.WriteLine($"Before Hashcheck: {stringToHash}");

    return GetSha512Hash(stringToHash);
}

string GetSha512Hash(string stringToHash)
{
    using SHA512 alg = new SHA512CryptoServiceProvider();
    var bytes = alg.ComputeHash(Encoding.UTF8.GetBytes(stringToHash));

    var sb = new StringBuilder();
    foreach (var b in bytes)
    {
        var hex = b.ToString("x2");
        sb.Append(hex);
    }

    return sb.ToString();
}
<?php
function GeneratePayoutVerificationRequestHash() {
    $PayoutId = "00000000-0000-0000-0000-000000000000";
    $siteCode = "[YOUR SITECODE]";
    $amount = 17.15;
    $MerchantReference = "123";
    $CustomerBankReference = "ABC123";
    $IsRtc = false;
    $NotifyUrl = "https://requestcatcher.com/";
    $BankGroupId = "13999FA-3A32-4E3D-82F0-A1DF7E9E4F7B";
    $AccountNumber = "ff313a955ad9a8ddff32cb734d49fbcddd8eeb1e235009d59a801bc5af78270cfd";
    $BranchCode = "198765";
    $apiKey = "[YOUR API KEY]";

    $inputString = $PayoutId . $siteCode . (int)($amount * 100) . $MerchantReference . $CustomerBankReference . $IsRtc . $NotifyUrl . $BankGroupId . $AccountNumber . $BranchCode . $apiKey;

    $calculatedHashResult = GenerateRequestHashCheck($inputString);
    echo "Hashcheck: " . $calculatedHashResult;
}

function GenerateRequestHashCheck($inputString) {
    $stringToHash = strtolower($inputString);
    echo "Before Hashcheck: " . $stringToHash;

    return GetSha512Hash($stringToHash);
}

function getSha512Hash($stringToHash) {
    $bytes = hash('sha512', $stringToHash, true);
    $hexString = bin2hex($bytes);
    return $hexString;
}

GeneratePayoutVerificationRequestHash();
?>



function GeneratePayoutVerificationRequestHash() {
var PayoutId = "00000000-0000-0000-0000-000000000000";
var siteCode = "[YOUR SITE CODE]";
var amount = 17.15;
var MerchantReference = "123";
var CustomerBankReference = "ABC123";
var IsRtc = false;
var NotifyUrl = "https://requestcatcher.com/";
var BankGroupId = "13999FA-3A32-4E3D-82F0-A1DF7E9E4F7B";
var AccountNumber = "ff313a955ad9a8ddff32cb734d49fbcddd8eeb1e235009d59a801bc5af78270cfd";
var BranchCode = "198765";
var apiKey = "[YOUR API KEY]";


var inputString = PayoutId + siteCode + Math.floor(amount * 100) + MerchantReference + CustomerBankReference + IsRtc + NotifyUrl + BankGroupId + AccountNumber + BranchCode + apiKey;

var calculatedHashResult = GenerateRequestHashCheck(inputString);
console.log("Hashcheck: " + calculatedHashResult);
}

function GenerateRequestHashCheck(inputString) {
var stringToHash = inputString.toLowerCase();
console.log("Before Hashcheck: " + stringToHash);

return GetSha512Hash(stringToHash);
}

function GetSha512Hash(stringToHash) {
var sha512 = new jsSHA("SHA-512", "TEXT");
sha512.update(stringToHash);
var hash = sha512.getHash("HEX");

return hash;
}

GeneratePayoutVerificationRequestHash();
import hashlib

def GeneratePayoutVerificationRequestHash():
PayoutId = "00000000-0000-0000-0000-000000000000"
siteCode = "[YOUR SITE CODE]"
amount = 17.15
MerchantReference = "123"
CustomerBankReference = "ABC123"
IsRtc = False
NotifyUrl = "https://requestcatcher.com/"
BankGroupId = "13999FA-3A32-4E3D-82F0-A1DF7E9E4F7B"
AccountNumber = "ff313a955ad9a8ddff32cb734d49fbcddd8eeb1e235009d59a801bc5af78270cfd"
BranchCode = "198765"
apiKey = "[YOUR API KEY]"

inputString = "{}{}{}{}{}{}{}{}{}{}".format(PayoutId, siteCode, int(amount*100), MerchantReference, CustomerBankReference, IsRtc, NotifyUrl, BankGroupId, AccountNumber, BranchCode,apiKey)

calculatedHashResult = GenerateRequestHashCheck(inputString)
print("Hashcheck: {}".format(calculatedHashResult))

def GenerateRequestHashCheck(inputString):
stringToHash = inputString.lower()
print("Before Hashcheck: {}".format(stringToHash))

return GetSha512Hash(stringToHash)

def GetSha512Hash(stringToHash):
sha512 = hashlib.sha512()
sha512.update(stringToHash.encode("utf-8"))
return sha512.hexdigest()

GeneratePayoutVerificationRequestHash()

Response

A successful verification should return a JSON object as described below.

PropertyTypeDescription
PayoutIdGuidThe unique identifier that should be used to identify the payout.
IsVerifiedboolWhether payout has been verified.
AccountNumberDecryptionKeyStringThe AES decryption key which will be used to decrypt the destination account number.
ReasonString (50)The reason why the payout verification was not valid.
{
  "PayoutId": "00000000-0000-0000-0000-000000000000",
  "IsVerified": true,
  "AccountNumberDecryptionKey": "C@OQN8oW9I8DSuKS$jfd",    
  "Reason": ""
}