Developers · 08

Example: Slack on stale

A Node server that verifies webhooks, lists stale components, and posts to Slack.

Raw markdown

Setup

  1. Token with projects:read + components:read.
  2. Webhook URL https://your-host/prototype/hooks, event spec.published, secret whsec_….
  3. 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"