Help · admin
Outbound webhooks
Wire Cuelist events into your own tooling. Event reference + HMAC verification recipe.
Webhooks let your tooling react to what happens in Cuelist — a new cue gets created, a project is archived, a teammate joins, a subscription ends. We POST a signed JSON envelope to an HTTPS URL you control; you respond with a 2xx and we move on.
Configure
Open Admin → Integrations, expand "Add endpoint," paste an HTTPS URL, and tick the events you want. The signing secret is shown once on creation — store it on your endpoint side immediately.
Delivery envelope
Every request carries:
X-Cuelist-Event— the event kind (e.g.cue.created).X-Cuelist-Delivery— a unique id for this delivery attempt. Use it to dedupe replays.X-Cuelist-Attempt— 1-indexed retry counter.X-Cuelist-Signature— the HMAC-SHA256 signature (see below).User-Agent: Cuelist-Webhook/1.0.- A JSON body shaped like:
{"kind":"cue.created","orgId":"...","occurredAt":"...","payload":{...}}
Verifying the signature
Compute HMAC-SHA256(secret, {timestamp} + "." + body) and compare to the v1 half of the signature header (t={ts},v1={hex}). Reject requests where the timestamp is more than 5 minutes off your clock — that defeats replay attacks.
Node.js example:
import crypto from "node:crypto";
function verify(req, secret) {
const header = req.headers["x-cuelist-signature"];
const [t, v1] = header.split(",");
const ts = t.slice(2);
const sig = v1.slice(3);
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${req.rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(sig),
Buffer.from(expected),
);
}Event kinds
project.created— new project. Payload:slug,title,subtitle,venue.project.updated— patch applied to project metadata. Payload:projectId,slug,patch.project.archived/project.deleted— project lifecycle.document.uploaded— new script bytes received. Payload:projectId,documentId,versionId,title,sizeBytes,converted(true when a Fountain or FDX upload was typeset to PDF on the way in).cue.created/cue.updated/cue.deleted— cue lifecycle.annotation.created/annotation.resolved— annotation lifecycle (producers ship in a follow-up release; subscribe now to opt in early).member.added— invite was accepted. Payload:userId,email,name.member.removed— member removed by an org admin.billing.subscription.active— Stripe subscription became active (covers new + reactivation).billing.subscription.updated— plan changed mid-term (upgrade/downgrade, proration applied).billing.payment_failed— an invoice payment failed; the subscription is at risk until it’s resolved.billing.subscription.canceled— Stripe sub deleted; org dropped back to free.
Retry policy
Any non-2xx response or a network timeout (30 s) triggers retry. Six attempts spaced exponentially:
- +1 minute
- +5 minutes
- +30 minutes
- +2 hours
- +12 hours
- +24 hours
After the sixth failure the delivery is marked failed and the endpoint's consecutive_failures counter increments. Ten consecutive failures auto-disables the endpoint — you'll see it greyed out in the admin UI until you re-enable. Each successful 2xx resets the counter to 0.
Replaying a delivery
The admin UI's "Recent deliveries" section has a Replay button next to each delivery. Replay re-queues the same payload (signature is recomputed with the current timestamp so the body bytes stay identical but the signature header rotates).
Tips
- Idempotency: store the
X-Cuelist-Deliveryid and short-circuit duplicate deliveries — retries can race a slow consumer. - Respond fast: do the work async. We don't care what your endpoint does internally, only that it returns 2xx within 30 s.
- Quiet 4xx: a 4xx response counts as a failed delivery. If you want to drop an event silently, return 204.