Developers · 08
Example: Slack on stale
A Node server that verifies webhooks, lists stale components, and posts to Slack.
Setup
- Token with
projects:read+components:read. - Webhook URL
https://your-host/prototype/hooks, eventspec.published, secretwhsec_…. - A Slack incoming-webhook URL.
Env: PROTOTYPE_URL, PROTOTYPE_TOKEN, PROTOTYPE_HOOK, SLACK_WEBHOOK.
server.mjs
javascript
import http from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";
const TOKEN = process.env.PROTOTYPE_TOKEN;
const HOOK_SECRET = process.env.PROTOTYPE_HOOK;
const BASE = process.env.PROTOTYPE_URL;
const SLACK = process.env.SLACK_WEBHOOK;
function verify(secret, timestamp, raw, signature) {
const expected = "sha256=" + createHmac("sha256", secret)
.update(`${timestamp}.${raw}`)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}
async function staleComponents() {
const headers = { Authorization: `Bearer ${TOKEN}` };
const projects = await fetch(`${BASE}/api/v1/projects`, { headers })
.then((r) => r.json());
const stale = [];
for (const p of projects.data ?? []) {
const comps = await fetch(
`${BASE}/api/v1/projects/${p.slug}/components`,
{ headers },
).then((r) => r.json());
for (const c of comps.data ?? []) {
if (c.latest?.healthStatus === "stale") {
stale.push(`${p.slug}/${c.slug}`);
}
}
}
return stale;
}
http.createServer(async (req, res) => {
if (req.method !== "POST" || req.url !== "/prototype/hooks") {
res.writeHead(404); res.end(); return;
}
const chunks = [];
for await (const ch of req) chunks.push(ch);
const raw = Buffer.concat(chunks).toString("utf8");
const ts = req.headers["x-prototype-timestamp"];
const sig = req.headers["x-prototype-signature"];
if (!verify(HOOK_SECRET, ts, raw, sig)) {
res.writeHead(401); res.end(); return;
}
const event = JSON.parse(raw);
if (event.type === "spec.published") {
const stale = await staleComponents();
if (stale.length && SLACK) {
await fetch(SLACK, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
text: `Spec published. ${stale.length} stale: ${stale.join(", ")}`,
}),
});
}
}
res.writeHead(200); res.end("ok");
}).listen(8787);Pull-only variant (no webhook):
bash
curl -s -H "Authorization: Bearer $PROTOTYPE_TOKEN" \
"$PROTOTYPE_URL/api/v1/projects/aurc-2026/components"