Skip to content

Verifying signatures

Every webhook is signed with your endpoint’s secret, so you can be sure it came from MeAPI and was not changed on the way.

X-MeAPI-Timestamp: 1793089000
X-MeAPI-Signature: sha256=5f2b…e1

The signature is the hex HMAC-SHA256 of the timestamp, a dot, and the raw request body, keyed with your secret:

signature = "sha256=" + hex( HMAC_SHA256( secret, timestamp + "." + raw_body ) )

To verify:

  1. Read the raw body bytes — before any JSON parsing. Re-serialising parsed JSON changes the bytes and breaks the signature.
  2. Compute the HMAC as above and compare it with X-MeAPI-Signature using a constant-time comparison.
  3. Reject requests whose timestamp is more than 5 minutes from your clock, to block replays.
// Express — note express.raw(), so req.body is the untouched Buffer
import express from 'express';
import crypto from 'node:crypto';
const app = express();
const SECRET = process.env.MEAPI_WEBHOOK_SECRET;
app.post('/meapi/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-MeAPI-Timestamp') || '';
const sig = req.get('X-MeAPI-Signature') || '';
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${ts}.${req.body}`).digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
const ok = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!fresh || !ok) return res.sendStatus(401);
res.sendStatus(200); // answer fast, then work
const event = JSON.parse(req.body);
console.log(event.event, event.data);
});
app.listen(3000);

Testing: press Send test in the panel. The delivery log there shows each attempt, your server’s status code and response, and lets you retry.

© 2026 MeAPI by TheWebStore028 · RADHAMADHAV WEBSTORE LLP · GSTIN 27ABJFR6148J1ZO · Contact us ·Cookie settings