Webhooks & verification
Verify deliveries, persist original events and make downstream processing idempotent.
Configure a receiver
POST /v1/webhook-endpoints configures endpoint_url, signing_secret and subscribed_event_types. Use HTTPS, the required entitlement and Idempotency-Key. Keep the signing secret in your receiver’s secret store; it is not a recoverable read field.
Verify before parsing
X-Osai-Signature is lowercase hexadecimal HMAC-SHA256 over event_id + a period + timestamp + a period + the exact raw body. Use X-Osai-Event-Id and RFC3339 X-Osai-Timestamp. Reject timestamps outside five minutes and compare in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
// Preserve the original Buffer before JSON parsing.
export function verifyDelivery(rawBody, headers, secret, now = Date.now()) {
const eventId = headers["x-osai-event-id"];
const timestamp = headers["x-osai-timestamp"];
const signature = headers["x-osai-signature"];
if (typeof eventId !== "string" || typeof timestamp !== "string" ||
typeof signature !== "string" || !/^[a-f0-9]{64}$/.test(signature)) return false;
const at = Date.parse(timestamp);
if (!Number.isFinite(at) || Math.abs(now - at) > 5 * 60 * 1000) return false;
const expected = createHmac("sha256", secret)
.update(eventId + "." + timestamp + ".")
.update(rawBody).digest();
return timingSafeEqual(expected, Buffer.from(signature, "hex"));
}Acknowledge durable receipt
After verification, record the event ID and original payload in a durable inbox before responding with 2xx. Process financial effects idempotently. A valid signature does not prove that an event has not already been processed.