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 within 8 seconds. Moveo abandons the delivery after that.
  3. Answer at the URL you configured. Redirects are not followed.
Your responseMoveo behavior
2xxSuccess — message marked as delivered.
429Temporary failure — retried after your Retry-After delay. See retry timing.
408Temporary failure — retried with exponential backoff.
Other 4xxPermanent failure — message marked as failed, no retry.
500, 502, 503, 504Temporary failure — retried with exponential backoff.
TimeoutPermanent failure — not retried.
3xxPermanent failure — redirects are not followed.
Timeouts are not retried

Because your endpoint has no way to tell Moveo whether a timed-out delivery was processed, a retry could duplicate the message. Moveo does not retry timeouts. Acknowledge the webhook as soon as you have stored the payload and do your processing asynchronously, rather than holding the response open.

Retry timing​

A delivery is attempted at most 3 times in total. How long Moveo waits between attempts depends on the response:

Your responseDelay before the next attempt
429 with a Retry-After headerThe delay you asked for, plus up to 20%, capped at 60 seconds.
Any other retryable responseExponential backoff — 250 ms, then 500 ms, capped at 2 seconds.

Retry-After is accepted either as a number of seconds or as an RFC 1123 timestamp. A value Moveo cannot parse, or one already in the past, falls back to exponential backoff.

Use 429 with Retry-After when you need Moveo to back off further than two seconds — for example, while your own rate limiter is shedding load. Note that the delay can reach a full minute, so the AI Agent's reply reaches your system that much later.