API · webhooks
Push, signed, verifiable.
Subscribe once and every state change comes to you — signed, so nobody can forge an “all clear”. Manage subscriptions under /webhook-subscriptions, including a test delivery and per-delivery inspection with redelivery.
Events
monitor.created | monitor.updated | monitor.deleted | monitor.status_changed | |
incident.opened | incident.acknowledged | incident.resolved | incident.reopened | incident.escalated |
heartbeat.missed | heartbeat.recovered | |||
status_page.update_published | webhook.test |
Delivery headers
X-Up4-Event | The event name, e.g. monitor.status_changed |
X-Up4-Delivery | Unique delivery id — use it for dedupe |
X-Up4-Signature | t=<unix>,v1=<hmac-sha256-hex> |
Verify the signature
HMAC-SHA256 over <timestamp>.<raw_body> with your
subscription secret. Reject timestamps outside a five-minute window.
Node
import crypto from "node:crypto";
export function verify(secret, header, rawBody, now = Math.floor(Date.now() / 1000)) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const ts = Number(parts.t);
if (!ts || Math.abs(now - ts) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.`)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}Go
func Verify(secret, header string, body []byte, now time.Time) bool {
parts := map[string]string{}
for _, part := range strings.Split(header, ",") {
k, v, ok := strings.Cut(part, "=")
if ok {
parts[k] = v
}
}
ts, err := strconv.ParseInt(parts["t"], 10, 64)
if err != nil || math.Abs(now.Sub(time.Unix(ts, 0)).Seconds()) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(fmt.Sprintf("%d.", ts)))
mac.Write(body)
got, err := hex.DecodeString(parts["v1"])
return err == nil && hmac.Equal(got, mac.Sum(nil))
}Destinations
Deliveries refuse loopback, link-local, private RFC 1918 and cloud-metadata destinations (SSRF protection). Test consumers end to end with a sandbox key — sandbox webhooks still deliver for real.