Skip to main content

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 the HmacSHA256 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

    /**
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));
}
}