Webhook Request Signature

Each webhook request that Quadrata emits will contain a header signature that an integrating application can use to verify the authenticity of the message.
Request signatures are sent as Base64 Encoded SHA384 strings, in the X-WEBHOOK-SIGNATURE request header.
The signature message used to sign and verify is the stringified JSON body payload.

Verifying The Request Signature

To verify the request signature, use Quadrata's public signing key for the respective environment.

Quadrata Public Signing Keys

Staging
Production
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOuY3rbyrujXxVEWq2X70uRa53ySTjwKR
j1ueDjYuzMegLrxIRiCXWMPtrVuqE0FcZ2YmJSiTaoDsq4yYMJw7fxi6nUj/8bzT
4+IxIok9qaEq9IbX6Bo/95vAu5bwO3rf
-----END PUBLIC KEY-----

Code Samples

NodeJS: Crypto
NodeJS: Express + Crypto
Python
// NOTE: this is using Quadrata's Staging webhook signing key
const publicKey = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----`;
/**
* Verify a signed message
* @param {string} message The request body stringified
* @param {string} signature The base64 encoded signature
* @returns {boolean}
*/
function verifySignature(message, signature) {
const signature = Buffer.from(signature, 'base64');
// create a verifier and verify the signature
const verifier = crypto.createVerify('sha384');
verifier.update(message);
return verifier.verify(publicKey, signature);
}
const crypto = require('crypto');
const express = require('express');
// NOTE: this is using Quadrata's Staging webhook signing key
const publicKey = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----`;
/**
* Verify a signed message
* @param {string} message The request body stringified
* @param {string} signature The base64 encoded signature
* @returns {boolean}
*/
function verifySignature(message, signature) {
const signature = Buffer.from(signature, 'base64');
// create a verifier and verify the signature
const verifier = crypto.createVerify('sha384');
verifier.update(message);
return verifier.verify(publicKey, signature);
}
// initialize the express application
const app = express();
// parse JSON request body into req.body
app.use(express.json());
// webhook request handler
app.post('/quadrata-webhook', (req, res) => {
// verify the request has a webhook signature
if (!('x-webhook-signature' in req.headers)
|| !req.headers['x-webhook-signature']
) {
return res.status(400).send();
}
// stringify the request body
const signatureMessage = JSON.stringify(req.body);
// extract the base64 signature from headers
const signature = request.headers['x-webhook-signature'];
// verify the signature
if (!verifySignature(signatureMessage, signature)) {
return res.status(400).send();
}
// webhook is valid, flush a successful response
res.status(200).send();
// process the event on your servers (you implement this)
processQuadrataWebhook(req.body)
.catch(console.error);
});
// listen for traffic
const port = process.ENV.port;
const hostname = process.ENV.hostname;
app.listen(port, hostname);
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, utils
# NOTE: using Quadrata's Staging webhook signing key
PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1iwh7gCfjdQRo/r82k8ErKiLO+cbPJkY
zqAqrPe0le6vjYY9aTp92ps37mcHzLjitslHeG4f5nSuBXKz8WXuwSyWhUW6EyZb
v/1tUfucvjBRrT7Yks6u6jmpwPmIuaqI
-----END PUBLIC KEY-----"""
def verify_signature(message: str, b64_signature: str) -> bool:
public_key = serialization.load_pem_public_key(
PUBLIC_KEY_PEM.encode('utf-8')
)
try:
public_key.verify(
base64.b64decode(b64_signature),
message.encode('utf-8'),
ec.ECDSA(hashes.SHA384()),
)
return True
except:
return False
If using json.dumps to marshal the JSON body payload into a string, be sure to override the separators:
json.dumps(request_body, separators=(',', ':'))