Umovi integration platform
Verify signatures
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.