Webhook signature verification
Webhooks are used to communicate events in the system as status changes or important notifications. The data contained in a Webhook call might sometimes hold sensitive data. To ensure that the Webhook originated from Pats, all our Webhooks are digitally signed.
Below you find a detailed description of the process to verify if a Webhook call originated from Pats:
Public key
To be able to verify Webhooks signature, you first need to download our public key:
Demo | Demo public key |
Live | Live public key |
Finding the signature
The signature can be found on the X-signature
HTTP header, encoded to Base64.
Verifying the signature
To verify the signature, you need from the Webhook Request: the body
, the X-signature
header and the public key available above.
Code examples
Java
import java.nio.file.*;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Webhook {
public static void main(String[] args) throws Exception {
String body = "TheRequestBodyGoesHere"; // the body from the Webhook request
String signature = "TheRequestXSignatureGoesHere"; // the X-Signature request header
String publicKeyPath = "path/to/the/downloaded/key/webhookPublicKey.pem"; // the path to the key
// pem to publicKey
String publicKeyPEM = (new String(Files.readAllBytes(Paths.get(publicKeyPath))))
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getMimeDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
// verify
Signature signer = Signature.getInstance("SHA1withRSA");
signer.initVerify(publicKey);
signer.update(body.getBytes());
if (signer.verify(java.util.Base64.getDecoder().decode(signature))) {
System.out.println("Signature verified");
} else {
System.out.println("ERROR: Signature not valid!");
}
}
}
NodeJS
const fs = require('fs'), path = require('path'), crypto = require('crypto'), verify = crypto.createVerify('SHA1');
let body = 'TheRequestBodyGoesHere'; // the body from the Webhook request
let signature = 'TheRequestXSignatureGoesHere'; // the X-Signature request header
let publicKeyPath = 'path/to/the/downloaded/key/webhookPublicKey.pem'; // the path to the key
let publicKey = fs.readFileSync(publicKeyPath); // read
verify.update(body); // sets the message to be verified
console.log(verify.verify(publicKey, signature, 'base64')); // test it
PHP
$body = 'TheRequestBodyGoesHere'; // the body from the Webhook request
$xSignature = 'TheRequestXSignatureGoesHere'; // the X-Signature request header
$publicKeyPath = 'path/to/the/downloaded/key/webhookPublicKey.pem'; // the path to the key
$base64DecodedXSignature = base64_decode($xSignature); // X-Signature is base64 encoded
$publicKey = openssl_pkey_get_public(file_get_contents($publicKeyPath));
echo openssl_verify($body, $base64DecodedXSignature, $publicKey); // test it
Python 3
import base64
from OpenSSL import crypto
public_key_path = 'path/to/the/downloaded/key/webhookPublicKey.pem' # the path to the key
def verify(message, signature, public_key_path):
try:
key = crypto.load_certificate(crypto.FILETYPE_PEM, open(key_path, 'rb').read())
crypto.verify(key, base64.urlsafe_b64decode(xsignature), message, 'sha1') # when successful, this call returns None
return True
except: # when unsuccessful, python raises an Error
return False
body = 'TheRequestBodyGoesHere' # the body from the Webhook request
xsignature = 'TheRequestXSignatureGoesHere' # the X-Signature from the Webhook request
print(verify(body, xsignature, public_key_path)) # test it
Ruby
require 'openssl'
require 'base64'
public_key_path = 'path/to/the/downloaded/key/webhookPublicKey.pem' # the path to the key
body = 'TheRequestBodyGoesHere' # the body from the Webhook request
signature = 'TheRequestXSignatureGoesHere' # the X-Signature request header
key = OpenSSL::X509::Certificate.new File.read public_key_path # creates the certificate object from path
print(key.public_key.verify(OpenSSL::Digest::SHA1.new, Base64.decode64(signature), body)) # test it
C#
This example uses the .pem file. If you first convert the .pem file to XML both the PemToXML
and GetXmlRsaKey
functions will not be necessary.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var Body = @"TheRequestBodyGoesHere"; // the body from the Webhook request
var Signature = "TheRequestXSignatureGoesHere"; // the X-Signature request header
var PublicKeyPath = @"path/to/the/downloaded/key/webhookPublicKey.pem"; // the path to the key
byte[] body = Encoding.UTF8.GetBytes(Body);
byte[] signature = Convert.FromBase64String(Signature); // X-Signature is base64 encoded
RSACryptoServiceProvider RSAVerifier = new RSACryptoServiceProvider();
RSAVerifier.FromXmlString(PemToXML(PublicKeyPath)); // Pem to XML
if (RSAVerifier.VerifyData(body, "SHA1", signature))
{
Console.WriteLine("Signature verified");
}
else
{
Console.WriteLine("ERROR: Signature not valid!");
}
Console.ReadLine();
}
private static string PemToXML(string PublicKeyPath)
{
StreamReader PubKeyReader = File.OpenText(PublicKeyPath);
string pem = PubKeyReader.ReadToEnd();
return GetXmlRsaKey(pem, obj =>
{
var publicKey = (RsaKeyParameters)obj;
return DotNetUtilities.ToRSA(publicKey);
}, rsa => rsa.ToXmlString(false));
}
private static string GetXmlRsaKey(string pem, Func<object, RSA> getRsa, Func<RSA, string> getKey)
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr = new StreamReader(ms))
{
sw.Write(pem);
sw.Flush();
ms.Position = 0;
PemReader pr = new PemReader(sr);
object keyPair = pr.ReadObject();
using (RSA rsa = getRsa(keyPair))
{
var xml = getKey(rsa);
return xml;
}
}
}
}
}