Merchants can easily validate webhooks received from the Fincra platform using the following steps:
Signature Inclusion: Every webhook sent includes a signature in the headers. This signature is the encrypted webhook data using the merchant's webhook secret key.
Validation on Merchant's End: Merchants can perform the same encryption process on their end using their webhook secret key. They can then compare the resulting signature with the one received. If the signatures match, the merchant can proceed with processing the webhook. However, if the signatures do not match, the merchant must discard the webhook and refrain from processing it.
import crypto from "crypto";
const encryptedData = crypto
.createHmac("SHA512", merchantWebhookSecretKey)
.update(JSON.stringify(payload))
.digest("hex");
const signatureFromWebhook = req.headers['signature'];
if(encryptedData === signatureFromWebhook) {
console.log("process");
}
else {
console.log("discard");
}
import hmac
import hashlib
import json
def encrypt_webhook_data():
webhook_secret_key = "The webhook secret key"
payload = "The webhook payload"
#encode the webhook key and save it in a variable
key = webhook_secret_key.encode("utf-8")
#convert the payload into serialized json
message = json.dumps(payload, separators=(',', ':')).encode("utf-8")
#encrypt the payload
encrypted_data = hmac.new(key, message, hashlib.sha512).hexdigest()
#get the signature from the header
signature = req.headers['signature'];
#check signature authencity
if signature == encrypted_data:
print("process")
else:
print("discard!")
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import org.json.*;
public class WebhookEncrypter {
public static void encryptWebhookData() {
String webhookSecretKey = "The webhook secret key";
String payload = "The webhook payload";
// encode the webhook key and save it in a variable
byte[] key = webhookSecretKey.getBytes(StandardCharsets.UTF_8);
// convert the payload into serialized JSON
String message = new JSONObject(payload).toString();
// encrypt the payload
Mac sha512Hmac;
try {
sha512Hmac = Mac.getInstance("HmacSHA512");
SecretKeySpec secretKey = new SecretKeySpec(key, "HmacSHA512");
sha512Hmac.init(secretKey);
byte[] hmacData = sha512Hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
String encryptedData = bytesToHex(hmacData);
// get the signature from the header
String signature = req.getHeader("signature");
// check signature authenticity
if (signature.equals(encryptedData)) {
System.out.println("process");
} else {
System.out.println("discard!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}
<script>
function generateHmacSHA512Signature(key, jsonData) {
// Convert JSON data to a string
const jsonString = JSON.stringify(jsonData);
// Convert key and data to Uint8Array
const keyBytes = new TextEncoder().encode(key);
const dataBytes = new TextEncoder().encode(jsonString);
// Compute HMAC SHA-512
const hmacSha512 = window.crypto.subtle.importKey(
"raw",
keyBytes,
{ name: "HMAC", hash: "SHA-512" },
true,
["sign"]
).then((key) => {
return window.crypto.subtle.sign(
"HMAC",
key,
dataBytes
);
});
return hmacSha512.then((signature) => {
// Convert the signature to hexadecimal format
return Array.from(new Uint8Array(signature)).map(byte => {
return ('0' + byte.toString(16)).slice(-2);
}).join('');
});
}
// Example usage:
const key = '6d1d475adef242e59a648335288ee80b';
const jsonData = {
"event": "payout.successful",
"data": {
"id": 40614,
"amountCharged": 350,
"amountReceived": 200,
"recipient": {
"name": "Customer Name",
"accountNumber": "1226742538",
"type": "individual",
"email": null
},
"fee": 150,
"rate": 1,
"paymentScheme": null,
"paymentDestination": "bank_account",
"sourceCurrency": "NGN",
"destinationCurrency": "NGN",
"status": "successful",
"createdAt": "2024-02-27T09:33:35.000Z",
"updatedAt": "2024-02-27T09:33:36.000Z",
"reference": "33aef44be6f7441a",
"customerReference": "test-006a133est",
"reason": "Disbursement was successful",
"traceId": null,
"valuedAt": "2024-02-27T09:33:36.000Z"
}
};
generateHmacSHA512Signature(key, jsonData)
.then((signature) => {
console.log("HMAC SHA-512 Signature:", signature);
var h3Element = document.getElementById("sig");
h3Element.textContent = signature;
})
.catch((error) => {
console.error("Error:", error);
});
</script>
NOTE
- In the context of our webhooks, a payload refers to an object that comprises the event and data fields of the webhook notification. To better understand the structure of our webhooks, please refer to sample payout webhook notification data . It is important to note that the webhook payload structure should remain encrypted and unmodified as received.
- Fincra events, including (Payouts, Conversions, Virtual Account,and Collections) are accompanied by a signature header. This header contains an HMAC SHA512 signature that is signed with your webhook secret key. Prior to processing the event, it is crucial to verify the signature provided in the header to ensure its authenticity.
Obtaining the Webhook Secret Key
To access and obtain your webhook secret key from the Fincra platform, please follow the instructions provided below for merchants:
Log in to your Fincra dashboard and navigate to the Profile section in the side menu.
On the Settings page, locate the API keys and webhook Configuration tab.
- Navigate to the API keys and webhooks section
- You can copy the webkook secret key here
- You can also reset the encryption keys in case of an exposure