Skip to content

Webhook: verifying the signature

Reference for web developers. Use HMAC-SHA256 and the signing secret to check that a webhook message really comes from meetpiloot and is not stale.

PlusFor developers1 min read
On this page1
  1. Example in Node.js (Express)
Available on Plus.

Every message carries the header X-Meetpiloot-Signature: sha256= followed by an HMAC-SHA256 over the exact body, with your signing secret as the key. Recompute it over the raw body and compare using a timing-safe function.

X-Meetpiloot-Timestamp holds the time the message was sent, in ISO 8601. Reject messages older than a few minutes, so an intercepted message cannot be replayed later.

Example in Node.js (Express)

JavaScript
const crypto = require('crypto');

function isFromMeetpiloot(rawBody, header, secret) {
  const expected =
    'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(header || '');
  const b = Buffer.from(expected);
  // timingSafeEqual throws on unequal lengths, so check that first.
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(a, b);
}

// Note express.raw(): the signature covers the exact bytes we sent,
// so verify before you parse or re-serialise the JSON.
app.post('/meetpiloot', express.raw({ type: 'application/json' }), (req, res) => {
  if (!isFromMeetpiloot(req.body, req.get('X-Meetpiloot-Signature'), process.env.MP_SECRET)) {
    return res.sendStatus(401);
  }
  const payload = JSON.parse(req.body.toString('utf8'));
  if (payload.event === 'lead.test') return res.sendStatus(200); // a test, not a real lead
  res.sendStatus(200);   // reply first...
  forwardToCrm(payload); // ...then do the slow work
});

When you create a new secret in your dashboard, the old one stops working immediately. So add the new secret to the receiving system straight away.

Stuck?

Email us or open a ticket from your dashboard. We reply within 1 to 2 working days.