Hooks signature verification
After confirming that the webhook endpoint is working as expected, secure the connection by implementing web hook verification.
This is especially important to verify that Fliqa generated a webhook request and that it didn’t come from a server acting like Fliqa.
Signature
Each web hook POST
request call will contain a X-Fliqa-Signature
header in the following format:
t=1696242888,v=1e49bf8db353a19c3924822601452a7aacfd4f24487b7d4568bc9c5f9ac7386b
or alternatively in case a new secret was regenerated withing 24 hours
t=1696242888,v=1e49bf8db353a19c3924822601452a7aacfd4f24487b7d4568bc9c5f9ac7386b,v0=f8f9c3507c01c2582241ca1b70c93aebea808757be800afbf374aeb402d4a101
where:
- t = epoch timestamp in seconds - when hook signature was created to prevent replay attacks
- v = the verification signature that is calculated from the secret, timestamp, hookUrl and body (JSON payload)
- v0 = the old verification signature that is calculated from the old secret if secret was regenerated altered in last 24 hours
The verification is calculated in the following way:
- time, hook URL and payload
JSON
body are combined into a single string like this:time.URL.body
- this string is then signed with the hook
secret
using theHmacSHA256
algorithm - output is encoded into hex string format
info
Use this online tool to check your algorithm matches ours!
Verification example
timestamp = 1698224457
URL = https://my.server.url/hook
body = {"paymentId":"00000000000000000000000000000000","status":"successful","created":"2023-10-25T08:59:27.327069Z","modified":"2023-10-25T09:00:57.327093Z","paymentData":null,"providerId":"hooked-bank","name":"Test hook call","description":"Manual hook trigger","pointOfSaleId":"fb6e0508e9714066b45ea598acfe2df4","sourceIban":"SI56010000000100090","sourceName":"Janez Novak","targetIban":"SI56044030255412331","targetName":"Fliqa top up","amount":1.23,"currency":"EUR","country":"SI","data":[{"key":"customer_id","value":"0000-00-0000"}],"locale":"en"}
secret = 0ddf43e8-43fa-46ce-8bb0-c6aab3c0b511
Should produce the following signature:
t=1698224457,
v=0a492fc70a2bf572e9eb05e66f8e490200ad6a68809d5501e23511efaf1814de
Verification implementation example
- Java
/**
See full implementation example at:
https://github.com/fliqa-io/examples/blob/main/java/src/main/java/io/fliqa/example/webhook/WebHookUtils.java
**/
package io.fliqa.example.webhook;
public class WebHookUtils {
private static final String DIGEST = "HmacSHA256";
public static boolean checkSignature(String signature, String secret, String oldSecret, String hookUrl, String body) {
// Split signature to t={time},v={verification},v0={old_verification}
String[] timeAndSignature = signature.split(",");
if (timeAndSignature.length < 2 || timeAndSignature.length > 3) {
throw new IllegalArgumentException(String.format("Invalid signature, expected time and verification but got: '%s'!", signature));
}
Long time = getSignatureTime(timeAndSignature);
String oldVerification = null;
String oldCompare = null;
if (timeAndSignature.length == 3) { // there is the old signature present (double check)
oldVerification = getOldSignatureVerification(timeAndSignature);
oldCompare = sign(oldSecret, time.toString(), hookUrl, body);
}
String verification = getSignatureVerification(timeAndSignature);
String compare = sign(secret, time.toString(), hookUrl, body);
return verification.equals(compare) || (oldVerification != null && oldVerification.equals(oldCompare));
}
protected static String sign(String secret, String time, String hookUrl, String content) {
String input = String.format("%s.%s.%s", time, hookUrl, content);
try {
Mac mac = Mac.getInstance(DIGEST);
mac.init(new SecretKeySpec(secret.getBytes(), DIGEST));
return toHexString(mac.doFinal(input.getBytes()));
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
// Failed to create SHA signature!
return "";
}
}
public static String toHexString(byte[] arg) {
return String.format("%x", new BigInteger(1, arg));
}
}