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 
JSONbody are combined into a single string like this:time.URL.body - this string is then signed with the hook 
secretusing theHmacSHA256algorithm - 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));
    }
}