Every delivery carries an X-Signature header: the string sha256= followed by the hex-encoded HMAC-SHA256 of the request body, keyed with the secret you were shown when the webhook was created. Recompute it over the raw bytes you received and compare with a constant-time comparison. If they differ, discard the request.
Node.js
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.STATUSBEE_WEBHOOK_SECRET;
// Keep the raw body: the signature is computed over the exact bytes we sent.
app.post("/statusbee", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const given = req.get("X-Signature") || "";
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return res.status(401).send("bad signature");
}
const event = JSON.parse(req.body);
// Acknowledge first, then do the work: a slow handler is retried.
res.status(204).end();
if (event.type === "monitor.down") {
console.log(`${event.data.name} is down: ${event.data.error}`);
}
});
app.listen(3000);Python
import hashlib
import hmac
import json
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["STATUSBEE_WEBHOOK_SECRET"].encode()
@app.post("/statusbee")
def statusbee():
raw = request.get_data() # the exact bytes, before any parsing
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
given = request.headers.get("X-Signature", "")
if not hmac.compare_digest(given, expected):
abort(401)
event = json.loads(raw)
if event["type"] == "incident.resolved":
print("resolved:", event["data"]["title"])
return "", 204Check a saved delivery by hand
Save the body of a delivery to a file (without trailing newline) and run:
# Recompute the signature for a saved delivery body
printf '%s' "$(cat delivery.json)" \
| openssl dgst -sha256 -hmac "$STATUSBEE_WEBHOOK_SECRET"The output should equal the X-Signature header without its sha256= prefix.
Rotating the secret
Secrets cannot be read back or rotated in place. To rotate, create a second webhook with the same URL and events, move your verifier to the new secret, then delete the old webhook. Both will deliver during the overlap, so the handler must accept either secret for a short while.
Status page subscribers who chose the webhook channel
A subscriber can subscribe to a public status page with a webhook URL instead of an email address. Those deliveries use the same envelope and headers, but the signing secret is that subscriber's unsubscribe token, not a workspace secret. They only receive incident and maintenance events.