Verify webhook signature
Verifying the webhook's signature is an optional step but highly recommended, that enables you to ensure that the message really originates from Fiant.
Code examples
public void verifyWebhookSignature(String payload, String xSignatureHttpHeader) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
String pemContent = IOUtils.resourceToString("/" + clientOptions.environment().getKeyName(), StandardCharsets.UTF_8);
// Sanitize the PEM string, Remove the header, footer, and any whitespace/newlines
String publicKeyPem = pemContent
.replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PUBLIC KEY-----", "")
.trim();
byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyPem);
// Reconstruct the Public Key
KeyFactory kf = KeyFactory.getInstance("Ed25519");
PublicKey publicKey = kf.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
// Initialize the Signature object
Signature sig = Signature.getInstance("Ed25519");
sig.initVerify(publicKey);
// Supply the data
sig.update(payload.trim().getBytes());
// Extract v1 signature from header
String signatureBase64 = Arrays.stream(xSignatureHttpHeader.split(","))
.map(String::trim) // Vital for HTTP headers which often have spaces
.filter(s -> s.regionMatches(true, 0, "v1=", 0, 3))
.map(s -> s.substring(3))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Signature verification failed - Missing signature"));
// Decode and verify the signature
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
if (!sig.verify(signatureBytes)) {
throw new IllegalStateException("Signature verification failed - Mismatch");
}
}
import base64
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
def verify_webhook_signature(payload: str, x_signature_header: str, pem_path: str):
# Load the public key
with open(pem_path, "rb") as key_file:
public_key = ed25519.Ed25519PublicKey.from_public_bytes(
ed25519.Ed25519PublicKey.from_pem(key_file.read()).public_bytes_raw()
)
# Extract v1 signature from header
parts = [p.strip() for p in x_signature_header.split(",")]
signature_b64 = next((p[3:] for p in parts if p.startswith("v1=")), None)
if not signature_b64:
raise ValueError("Signature verification failed - Missing signature")
signature_bytes = base64.b64decode(signature_b64)
try:
# Verify (stripping payload to match Java's .strip())
public_key.verify(signature_bytes, payload.strip().encode('utf-8'))
except InvalidSignature:
raise ValueError("Signature verification failed - Mismatch")import * as crypto from 'crypto';
import * as fs from 'fs';
function verifyWebhookSignature(payload: string, xSignatureHeader: string, pemPath: string): void {
const pemContent = fs.readFileSync(pemPath, 'utf8');
// Extract v1 signature
const signatureBase64 = xSignatureHeader
.split(',')
.map(s => s.trim())
.find(s => s.startsWith('v1='))
?.substring(3);
if (!signatureBase64) {
throw new Error("Signature verification failed - Missing signature");
}
const isVerified = crypto.verify(
undefined,
Buffer.from(payload.trim()),
{
key: pemContent,
format: 'pem',
},
Buffer.from(signatureBase64, 'base64')
);
if (!isVerified) {
throw new Error("Signature verification failed - Mismatch");
}
}Updated 17 days ago