// Express example:
const crypto = require('node:crypto');
const express = require('express');
const BUNDLEUP_WEBHOOK_SECRET = process.env.BUNDLEUP_WEBHOOK_SECRET;
function verifySignature(headerSignatureString, rawBody) {
if (typeof headerSignatureString !== 'string') {
return false;
}
const headerSignature = Buffer.from(headerSignatureString, 'hex');
const computedSignature = crypto
.createHmac('sha256', BUNDLEUP_WEBHOOK_SECRET)
.update(rawBody)
.digest();
return crypto.timingSafeEqual(computedSignature, headerSignature);
}
const app = express();
app.post(
'/webhook',
express.json({
verify: (req, _res, buf) => {
// Capture the raw body for signature verification.
req.rawBody = buf;
},
}),
(req, res) => {
if (!verifySignature(req.get('bundleup-signature'), req.rawBody)) {
return res.sendStatus(401);
}
if (Math.abs(Date.now() - req.body.webhookTimestamp) > 60 * 1000) {
// Reject any webhooks not within 60 seconds of the current time to prevent replay attacks.
return res.sendStatus(401);
}
try {
// ... Handle verified webhook ...
return res.sendStatus(200);
} catch (err) {
// Indicate to Linear that there was a server error so the webhook is retried later.
return res.sendStatus(500);
}
},
);
app.listen(8080, () => console.log('Serving on port 8080'));