To ensure the authenticity, integrity, and reliability of webhook notifications from the Finix platform, merchants must verify the Signature
field included in the webhook HTTP headers using RSA with SHA512 (SHA512withRSA
).
Whenever the Finix platform sends a webhook to notify merchants of transaction status or updates, the merchant is required to verify the digital signature in the request header to confirm the source and content have not been tampered with.
Finix includes a Signature
field in the webhook request header. The merchant must compute the verification string using the requestBody
andTimestamp
header, and validate it using Finix's public key.
string_to_verify = SHA512(body) + Timestamp
body
: The raw JSON string from the webhook request body (field order must be preserved)timestamp
: The Unix timestamp from the request header (in seconds){"status":"SUCCESS","orderId":"ABC123","amount":100}
09ec4d91b7dd1fce70320b09f3fd7e98cd66efc8f918bb56e98a37c6b179f7d24fc4e43c0a614f8b9e5e95e410af0c0fd9d5f40ad8e38b30a9ad512b48c9c0e7
1699447297
09ec4d91b7dd1fce70320b09f3fd7e98cd66efc8f918bb56e98a37c6b179f7d24fc4e43c0a614f8b9e5e95e410af0c0fd9d5f40ad8e38b30a9ad512b48c9c0e71699447297
Signature
value from the header must be Base64-decoded before verificationFinix uses its private key to sign the webhook data. The merchant uses the public key to verify it. This ensures that: The webhook is genuinely from Finix The data has not been tampered with The request is not replayed or forged
Field case sensitivity matters — the signature string must match exactly Keep the JSON body unchanged — avoid reformatting, reordering fields, or adding whitespace Signature
is Base64-encodedTimestamp
must be extracted from the request headers
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class CallbackVerifier {
public static void main(String[] args) throws Exception {
String body = "{\"status\":\"SUCCESS\",\"orderId\":\"ABC123\",\"amount\":100}";
String timestamp = "1699447297";
String signatureBase64 = "REQUEST_HEADER_SIGNATURE_VALUE";
String bodyHash = sha512Hex(body);
String stringToVerify = bodyHash + timestamp;
String publicKeyPem = "-----BEGIN PUBLIC KEY-----\nYOUR_FINIX_PUBLIC_KEY_HERE\n-----END PUBLIC KEY-----";
PublicKey publicKey = loadPublicKey(publicKeyPem);
boolean isValid = verifySignature(publicKey, stringToVerify, signatureBase64);
System.out.println("Signature valid: " + isValid);
}
public static String sha512Hex(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) sb.append(String.format("%02x", b));
return sb.toString();
}
public static PublicKey loadPublicKey(String pem) throws Exception {
pem = pem.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
byte[] decoded = Base64.getDecoder().decode(pem);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
return KeyFactory.getInstance("RSA").generatePublic(spec);
}
public static boolean verifySignature(PublicKey publicKey, String data, String signatureBase64) throws Exception {
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
Signature verifier = Signature.getInstance("SHA512withRSA");
verifier.initVerify(publicKey);
verifier.update(data.getBytes(StandardCharsets.UTF_8));
return verifier.verify(signatureBytes);
}
}