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;
                }
            }
        }
    }
}