The webhook channel
The webhook channel tells your own systems when files are regenerated. On every regeneration, Sichta POSTs a signed JSON payload to a URL you choose, carrying the file list, their hashes, and a download link for the bundle.
Setup
- In the app, open your site → Channel and find Webhook.
- Enter your endpoint. It must be
https— the signature proves who sent a payload, but only TLS keeps the file list private in transit. - Save. A signing secret is shown once; store it before leaving the page. Saving a URL again mints a new secret, so a webhook that moves to a different service cannot keep using the old key.
The payload
{
"event": "artifact_set.created",
"site": { "hostname": "your-site.com" },
"artifactSet": {
"number": 7,
"createdAt": "2026-08-20T04:31:02.000Z",
"files": [{ "path": "llms.txt", "sha256": "…", "bytes": 1299 }]
},
"downloadUrl": "https://web.sichta.gigliotti.software/bundles/…"
}
The downloadUrl is signed and valid for 24 hours. It stops working if the subscription has ended beyond its grace period, same as the managed routes.
Verifying the signature
Every request carries Sichta-Signature: t=<unix>,v1=<hex>, where v1 is HMAC-SHA256 of <t>.<raw body> using your secret. Verify against the raw body, before any JSON parsing — re-serialising changes the bytes and the signature will not match.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
Reject anything older than five minutes: the timestamp is inside the signed material precisely so a captured payload stops being replayable.
Retries
A failed delivery is retried three times, one second apart and doubling. A 5xx or a connection failure is retried; a 4xx is not — your endpoint understood the request and refused it, and repeating a rejected request would just be noise. Failures are reported on the channel page.
Delivery never blocks a regeneration: if your endpoint is down, the new set still exists and is still downloadable from the app.