Get notified when a dimension crosses the 50 response minimum or the certificate updates.
dimension.reportedevent
A dimension reached 50 responses.
certificate.updatedevent
Daily certificate refresh finished.
usage.thresholdevent
Shown check-ins passed a number you set.
None of them carries an answer. You learn that something is now reportable, never what anyone said.
Verifying a delivery
Every request carries a `moyo-signature` header holding a timestamp and an HMAC-SHA256 of that timestamp joined to the raw body. Verify against the raw bytes, before parsing: verifying a re-serialised object is verifying something other than what arrived, which is the usual way this check gets quietly defeated.
js
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, header, rawBody) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=").map((s) => s.trim())),
);
// Reject anything older than five minutes, so a captured delivery
// cannot be replayed next week as though it were new.
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Retries
Five attempts, backing off one minute, five, twenty-five, two hours, ten hours. A delivery is never retried with a different body: the payload is stored when the event happens, so a retry three hours later says what was true then rather than what is true now — which is what you need to reconcile your own records.
An endpoint that fails five deliveries in a row is switched off, and the dashboard says why. A webhook that silently stops is worse than none: you build on it, it stops, and nobody finds out.