Verifying signatures
DocsAPI referenceAdvanced
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: 1793089000X-MeAPI-Signature: sha256=5f2b…e1The 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:
- Read the raw body bytes — before any JSON parsing. Re-serialising parsed JSON changes the bytes and breaks the signature.
- Compute the HMAC as above and compare it with
X-MeAPI-Signatureusing a constant-time comparison. - 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 Bufferimport 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);<?php$secret = getenv('MEAPI_WEBHOOK_SECRET');$raw = file_get_contents('php://input');$ts = $_SERVER['HTTP_X_MEAPI_TIMESTAMP'] ?? '';$sig = $_SERVER['HTTP_X_MEAPI_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret);if (abs(time() - (int) $ts) > 300 || !hash_equals($expected, $sig)) { http_response_code(401); exit;}
http_response_code(200);$event = json_decode($raw, true);// $event['event'], $event['data'] …# Flaskimport hashlib, hmac, os, timefrom flask import Flask, request, abort
app = Flask(__name__)SECRET = os.environ["MEAPI_WEBHOOK_SECRET"].encode()
@app.post("/meapi/webhook")def meapi_webhook(): raw = request.get_data() # raw bytes ts = request.headers.get("X-MeAPI-Timestamp", "") sig = request.headers.get("X-MeAPI-Signature", "") expected = "sha256=" + hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest() if abs(time.time() - int(ts or 0)) > 300 or not hmac.compare_digest(expected, sig): abort(401) event = request.get_json() # event["event"], event["data"] … return "", 200Testing: press Send test in the panel. The delivery log there shows each attempt, your server’s status code and response, and lets you retry.
Was this page helpful?
Thank you — we read every comment.
Could not send right now. Please try again later.
© 2026 MeAPI by TheWebStore028 · RADHAMADHAV WEBSTORE LLP · GSTIN 27ABJFR6148J1ZO · Contact us ·Cookie settings