Developers

Umovi integration platform

Verify signatures

HMAC SHA-256 verification using the raw request body and delivery timestamp.

Every delivery includes:

X-Umovi-Event: booking.created
X-Umovi-Delivery-Id: 22ed489c-0376-46ad-931b-1e00ecae2310
X-Umovi-Timestamp: 1785319200
X-Umovi-Signature: sha256=9d4a…

Umovi computes the signature as:

sha256=hex(HMAC-SHA256(secret, timestamp + "." + rawBody))

Do not reserialize parsed JSON. Whitespace and property ordering must remain exactly as received.

Node.js example

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyUmoviWebhook(
  secret: string,
  timestamp: string,
  rawBody: string,
  receivedSignature: string,
  nowSeconds = Math.floor(Date.now() / 1000),
) {
  // Receiver policy: allow at most five minutes of clock skew.
  if (!/^\d+$/.test(timestamp)) return false;
  const sentAt = Number(timestamp);
  if (!Number.isSafeInteger(sentAt) || Math.abs(nowSeconds - sentAt) > 300) {
    return false;
  }
  const digest = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
  const expected = Buffer.from(`sha256=${digest}`);
  const received = Buffer.from(receivedSignature);

  return expected.length === received.length && timingSafeEqual(expected, received);
}

The example uses a five-minute receiver tolerance; adjust it to your clock synchronization policy. Each retry is signed with its current delivery timestamp, not the original event time. Deduplicate by the envelope id or X-Umovi-Delivery-Id. This limits replay attacks even when a signed request is captured.

During secret rotation, replace the stored secret atomically with the value returned by /rotate-secret. The replacement is shown once. Subsequent attempts, including retries of older payloads, use the current secret. Umovi does not provide a server-side overlap period; account for already in-flight requests during a coordinated rotation.