Skip to main content

Webhook security

Webhook signature verification

All webhook requests include an HMAC-SHA256 signature in the X-Moveo-Signature header. Always verify this signature before processing the request.

Algorithm:

signature = HEX(HMAC-SHA256(webhook_secret, request_body))
const crypto = require("crypto");

function verifySignature(body, signature, secret) {
const computed = crypto
.createHmac("sha256", secret)
.update(body, "utf8")
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(signature));
}

// Express.js middleware example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-moveo-signature"];
const isValid = verifySignature(req.body, signature, WEBHOOK_SECRET);

if (!isValid) {
return res.status(401).send("Invalid signature");
}

const event = JSON.parse(req.body);
// Process event...
res.status(200).send("OK");
});

Replay attack prevention

Every webhook payload includes a root timestamp field (Unix milliseconds). To guard against replay attacks, compare this value to the current time and reject requests older than your tolerance window (e.g., 5 minutes). Always verify the signature first — the timestamp is only trustworthy if the payload has not been tampered with.


Webhook response requirements

Your webhook endpoint must:

  1. Return HTTP 2xx status (200-299) to acknowledge receipt.
  2. Respond quickly (within a few seconds).
Your responseMoveo behavior
2xxSuccess — message marked as delivered.
4xxPermanent failure — message marked as failed.
5xxTemporary failure — retried with exponential backoff.
TimeoutTemporary failure — retried with exponential backoff.