Updated 25 October 2026 · By the Upcertain team
Set it up
- In Upcertain, open Settings, Alert channels and choose Webhook. Owners, admins and editors can add channels.
- Paste the webhook URL. Use an
https://address that is reachable from the internet; private and local addresses are refused. Add a signing secret so you can verify each request. - Pick the events to send: Down, Up (recovery) and SSL certificate expiry.
- Tick Pre-select on new monitors if this should be the default for monitors you create from now on. Existing monitors keep their own choice.
- Save, then use Send a test in the channel list to post a test message.
- Open each monitor that should alert here and add the channel under its alert settings, with an optional delay and repeat interval.
Events
| Event | Sent when |
|---|---|
monitor.down | An incident opens after a confirmed failure |
monitor.still_down | A reminder, if the monitor's channel has a repeat interval |
monitor.up | The monitor recovers and the incident resolves |
ssl.expiring | A certificate reaches one of its warning days |
monitors.down_grouped | Alert grouping combined several failures into one alert |
test.channel, test.down, test.up | You sent a test from the app |
Request
Each delivery is a POST with a JSON body and these headers:
X-Upcertain-Event: the event name.X-Upcertain-Timestamp: Unix seconds when the request was signed.X-Upcertain-Delivery: a unique id; the same id is reused when a delivery is retried, so you can ignore duplicates.X-Upcertain-Signature:v1=followed by the hex HMAC-SHA256 oftimestamp + "." + body, keyed with your signing secret. Sent only when the channel has a secret.
{
"delivery_id": "6f1c...",
"sent_at": "2026-10-25T09:14:03.000Z",
"event": "monitor.down",
"title": "Down: payments-api",
"summary": "payments-api is down: HTTP 503.",
"account": "Acme",
"fields": [
{ "label": "Monitor", "value": "https://api.example.com/health" },
{ "label": "Root cause", "value": "HTTP 503" },
{ "label": "Started", "value": "25 Oct 2026, 09:13:21 UTC" }
],
"links": {
"acknowledge": "https://upcertain.com/incidents/<id>?ack=1",
"view_incident": "https://upcertain.com/incidents/<id>"
},
"monitor": { "id": "...", "name": "payments-api", "type": "http", "target": "https://api.example.com/health" },
"incident": {
"id": "...",
"started_at": "2026-10-25T09:13:21.000Z",
"resolved_at": null,
"cause": "status_code",
"cause_detail": "HTTP 503",
"status_code": 503
}
}ssl.expiring adds days_left, threshold_days and expires_at. monitors.down_grouped adds a monitors list. Treat unknown fields as optional: new ones may be added.
Verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received, before JSON parsing.
export function verify(rawBody: string, headers: Headers, secret: string): boolean {
const ts = headers.get("x-upcertain-timestamp") ?? "";
const sig = headers.get("x-upcertain-signature") ?? "";
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // reject replays
const expected = "v1=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
return sig.length === expected.length && timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}import hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
ts = headers.get("X-Upcertain-Timestamp", "")
sig = headers.get("X-Upcertain-Signature", "")
if abs(time.time() - int(ts or 0)) > 300:
return False
mac = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig, "v1=" + mac)Delivery and retries
Answer with any 2xx status within a few seconds. Anything else, or no answer, is retried at 30 seconds, 1 minute, 2 minutes and 4 minutes; after the fifth attempt the delivery is marked failed. Deliveries are at least once, so use X-Upcertain-Delivery to drop duplicates.
Questions
Can I receive webhooks on a private network address?
No. To protect both sides, Upcertain refuses webhook URLs that resolve to private, loopback or link-local addresses.
Is there an API to read monitors and incidents as well?
Yes. The REST API and the MCP server are described in the MCP and API docs.