Setting up webhooks
What you'll learn
- How to create a webhook handler
- How to validate the origin of the webhook request
- Best practices for managing traffic
Provisioning of webhooks is currently done manually, please contact us to do so.
Validating webhooks request origin
Every request that is sent from Partly will contain a partly-hmac-sha256 header. This header is a base64 encoded HMAC signature that is produced by signing the request payload (body) with the shared webhook secret provided during webhook provisioning. As well as this, each body contains a timestamp of when the notification is sent. Below are the list of steps required to verify that Partly was the origin of the request. Followed by a TypeScript example
- Hash the raw body bytes using the HMAC-SHA256 algorithm
You must be careful that any middleware / frameworks in use leaves the order of fields preserved in the body
- Take the base64 encoded
partly-hmac-sha256and decode it into its bytes - Do a secure comparison of the hash generated in step 1 and the decoded signature from step 2
- Validate the timestamp within the body is within the last 5 minutes. This field will always be named timestamp and the formatting of the timestamp will be as per RFC3339.
Once the hash is successfully compared, you can assume the inbound request was made by the integration
// Do not store your webhook key in plaintext in your repository
const express = require('express');
const crypto = require('crypto');
const app = express();
const webhookSecret = 'pwh_507c1c992d3a5760ba4ecb1a18dfc9cc';
app.use(express.raw({ type: '*/*' }));
app.post('/partly-webhook.accept', async (req, res) => {
const partlyHMACSignature = req.headers['partly-hmac-sha256'] as string;
if (!partlyHMACSignature) {
res.status(400).send("Missing signature header");
}
const calculatedHmacDigest = crypto.createHmac('sha256', webhookSecret).update(JSON.stringify(req.body)).digest('base64');
const hmacValid = crypto.timingSafeEqual(Buffer.from(calculatedHmacDigest, 'base64'), Buffer.from(partlyHMACSignature, 'base64'));
if (!hmacValid) {
res.status(401).send('HMAC validation failed');
}
const timestamp = req.body.timestamp;
if (!timestamp) {
return res.status(400).send("Missing timestamp field");
}
const ts = new Date(timestamp);
const now = new Date();
// Check that the timestamp is within the last 5 minutes
if (ts.getTime() > now.getTime() || ts.getTime() < now.getTime() - 5 * 60 * 1000) {
return res.status(401).send("Timestamp is invalid");
}
res.status(200).send();
});
Webhook Body Model
Currently, no webhooks are publically exposed. Check back soon!