Integration Implementation Examples
const express = require('express');
const crypto = require('crypto');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 8080;
const SIGNING_SECRET = process.env.SIGNING_SECRET;
const TOLERANCE_PERIOD = 5 * 60 * 1000; // 5 minutes in milliseconds
// Middleware to preserve raw body for signature verification
const rawBodySaver = function (req, res, buf, encoding) {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
// Function to calculate HMAC signature
function calculateHMAC(payloadWithTimestamp) {
const hmac = crypto.createHmac('sha256', SIGNING_SECRET);
hmac.update(payloadWithTimestamp);
return hmac.digest('base64');
}
// Middleware to verify webhook signature and prevent replay attacks
function verifyWebhookSignature(req, res, next) {
const timestamp = req.headers['paynow-timestamp'];
const providedSignature = req.headers['paynow-signature'];
if (!timestamp || !providedSignature) {
return res.status(400).send('Missing required headers');
}
const timestampInt = parseInt(timestamp, 10);
if (isNaN(timestampInt)) {
return res.status(400).send('Invalid timestamp format');
}
const timestampTime = new Date(timestampInt);
const currentTime = new Date();
if (currentTime - timestampTime > TOLERANCE_PERIOD) {
return res.status(401).send('Timestamp out of tolerance');
}
const payloadWithTimestamp = `${timestamp}.${req.rawBody}`;
const expectedSignature = calculateHMAC(payloadWithTimestamp);
const signatureValid = crypto.timingSafeEqual(Buffer.from(providedSignature, 'base64'), Buffer.from(expectedSignature, 'base64'));
if (!signatureValid) {
return res.status(401).send('Invalid signature');
}
next();
}
app.use(express.json({ verify: rawBodySaver }));
app.post('/webhook', verifyWebhookSignature, (req, res) => {
const eventType = req.body.event_type;
switch (eventType) {
case 'ON_DELIVERY_ITEM_ADDED':
// handleOnDeliveryItemAdded(req.body);
break;
case 'ON_DELIVERY_ITEM_ACTIVATED':
// handleOnDeliveryItemActivated(req.body);
break;
case 'ON_DELIVERY_ITEM_USED':
// handleOnDeliveryItemUsed(req.body);
break;
case 'ON_DELIVERY_ITEM_REVOKED':
// handleOnDeliveryItemRevoked(req.body);
break;
default:
console.log(`Received unknown event type: ${eventType}`);
}
res.status(200).send('Webhook processed');
});
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
Last updated
Was this helpful?