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?