1. HMAC-SHA512
This is the default method. Each event payload is signed using HMAC-SHA512 with your providedsigning_key. The signature is sent in the xpay-signature header.
private boolean verifySignature(String incomingSignature, String payload, String secret) throws Exception {
Mac sha512Hmac = Mac.getInstance("HmacSHA512");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA512");
sha512Hmac.init(keySpec);
byte[] macData = sha512Hmac.doFinal(payload.getBytes("UTF-8"));
incomingSignature.equals(Base64.getEncoder().encodeToString(macData));
}
const crypto = require('crypto');
function verifySignature(incomingSignature, payload, secret) {
const hmac = crypto.createHmac('sha512', secret);
hmac.update(payload);
const generatedSignature = hmac.digest('base64');
return incomingSignature === generatedSignature;
}
// Usage example
const incomingSignature = 'some-signature';
const payload = 'payload-data';
const secret = 'secret-key';
const isValid = verifySignature(incomingSignature, payload, secret);
console.log(isValid);
import hmac
import hashlib
import base64
def verify_signature(incoming_signature, payload, secret):
# Create the HMAC-SHA512 signature
mac = hmac.new(secret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha512)
generated_signature = base64.b64encode(mac.digest()).decode('utf-8')
return incoming_signature == generated_signature
# Usage example
incoming_signature = 'some-signature'
payload = 'payload-data'
secret = 'secret-key'
is_valid = verify_signature(incoming_signature, payload, secret)
print(is_valid)
// Add Dependencies in Cargo.toml
// [dependencies]
// hmac = "0.11"
// sha2 = "0.10"
// base64 = "0.13"
use hmac::{Hmac, Mac};
use sha2::Sha512;
use base64::{encode};
// Type alias for HMAC-SHA512
type HmacSha512 = Hmac<Sha512>;
fn verify_signature(incoming_signature: &str, payload: &str, secret: &str) -> bool {
let mut mac = HmacSha512::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size");
mac.update(payload.as_bytes());
let result = mac.finalize();
let generated_signature = encode(result.into_bytes());
incoming_signature == generated_signature
}
// Usage example
fn main() {
let incoming_signature = "some-signature";
let payload = "payload-data";
let secret = "secret-key";
let is_valid = verify_signature(incoming_signature, payload, secret);
println!("{}", is_valid);
}
2. ECDSA
xPay supports ECDSA signature verification for webhooks. The signature is sent in thexpay-private-signature header, and the public key is provided below.
xPay Webhook Public Key
This is the public key used for webhook signature verification. The key below is base64 encoded. You can use this key to verify ECDSA signatures from xPay webhooks.MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
public class WebhookVerifier {
public static boolean verifySignature(String publicKeyBase64, String signatureBase64, String payload) throws Exception {
byte[] publicBytes = Base64.getDecoder().decode(publicKeyBase64);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA");
ecdsaVerify.initVerify(pubKey);
ecdsaVerify.update(payload.getBytes("UTF-8"));
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
return ecdsaVerify.verify(signatureBytes);
}
public static void main(String[] args) throws Exception {
String publicKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==";
String signature = "MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=";
String payload = "{\"eventId\":\"whe_g00AGgDy9KeiTPj6\",...}";
boolean isValid = verifySignature(publicKey, signature, payload);
System.out.println("Signature valid: " + isValid);
}
}
const crypto = require('crypto');
function verifySignature(publicKeyBase64, signatureBase64, payload) {
const publicKeyPem = `-----BEGIN PUBLIC KEY-----\n${publicKeyBase64.match(/.{1,64}/g).join('\n')}\n-----END PUBLIC KEY-----`;
const verifier = crypto.createVerify('SHA256');
verifier.update(payload);
verifier.end();
const signature = Buffer.from(signatureBase64, 'base64');
return verifier.verify(publicKeyPem, signature);
}
// Example usage
const isValid = verifySignature(
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
"MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
'{"eventId":"whe_g00AGgDy9KeiTPj6",...}'
);
console.log('Signature valid:', isValid);
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_der_public_key
def verify_signature(public_key_base64, signature_base64, payload):
public_key_bytes = base64.b64decode(public_key_base64)
public_key = load_der_public_key(public_key_bytes)
signature = base64.b64decode(signature_base64)
try:
public_key.verify(signature, payload.encode('utf-8'), ec.ECDSA(hashes.SHA256()))
return True
except Exception:
return False
# Example usage
is_valid = verify_signature(
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
"MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
'{"eventId":"whe_g00AGgDy9KeiTPj6",...}'
)
print("Signature valid:", is_valid)
use base64;
use ring::{signature, signature::UnparsedPublicKey};
fn verify_signature(public_key_b64: &str, signature_b64: &str, payload: &str) -> bool {
let public_key_bytes = match base64::decode(public_key_b64) {
Ok(bytes) => bytes,
Err(_) => return false,
};
let signature_bytes = match base64::decode(signature_b64) {
Ok(bytes) => bytes,
Err(_) => return false,
};
let public_key = UnparsedPublicKey::new(&signature::ECDSA_P256_SHA256_ASN1, &public_key_bytes);
public_key.verify(payload.as_bytes(), &signature_bytes).is_ok()
}
fn main() {
let valid = verify_signature(
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBlXbsIwGSYhdXBOtdrZr3L346JXi3dOg8vP9NCcTOV0ucLVl/GPi2ZVMsdBISQKTGIhzXY/gddRl846C27TfPw==",
"MEUCIQDncKxd1HEthoRD/+kX2Kw0ZKRbaewtX9IfvW5vE/MPMwIgZ/BLckOaRkHqP3mtIX4DQRRKLXvmt9mmWG3mKMNUZPI=",
r#"{"eventId":"whe_g00AGgDy9KeiTPj6",...}"#
);
println!("Signature valid: {}", valid);
}
Sample Payload Header
{
"xpay-signature" : "s3z2v3ZGwMvDwiQ+9PpAT3WuSYU+PeSZJqvdzZXPmifAh3sT+s502PgJJx9NV4KQaScEaquTEuHJ30v17hK5GA==",
"xpay-private-signature" : "MEYCIQD+649oWWsMkVbTcOsfkxKvxsnbAPbeq0QHptC2i1dFuwIhALN5GHEliiv+MT3/5a2ye8Wbud5rFNDRIMz6ExgWAaYg",
...
}