Slidereel docs
Webhooks
Get notified when a Slidereel render completes or fails. HMAC-SHA256 signed delivery to any HTTPS endpoint — wire it into Slack, Discord, Notion, Airtable, Make, n8n, your own server, or anything else that listens for an HTTP POST.
Setup
- Open the Billing page and find the Webhook card.
- Paste your endpoint URL and click Save webhook. We'll return a signing secret once — copy and store it; it cannot be retrieved later.
- Click Send test event to confirm delivery and inspect the payload shape.
HTTPS is required. Localhost, IP literals, and any URL that resolves to a private network range are rejected at save time.
Events
Three event types fire today:
render.completed— fires after a successful video render upload and credit charge. Thevideo_urlis a short-lived signed URL (download or copy promptly).render.failed— fires when a render attempt that reached the tracking stage fails. Includes the error message.test.ping— fires only from the "Send test event" button for diagnostics.
Payload format
Body is JSON with a stable envelope. delivery_id is a unique UUID-style string per delivery — use it as an idempotency key in your handler.timestamp is Unix seconds at fire time.
{
"event": "render.completed",
"timestamp": 1746288000,
"delivery_id": "a3f1c2b6e9d8f04812ab7c40",
"data": {
"render_id": "...",
"history_id": "...",
"topic": "5 productivity myths",
"platform": "tiktok",
"transition": "fade",
"video_url": "https://storage.googleapis.com/...signed...",
"storage_path": "users/{uid}/projects/{folder}/videos/{id}.mp4",
"project_folder": "my-project",
"slide_count": 8,
"credits_spent": 62
}
}{
"event": "render.failed",
"timestamp": 1746288000,
"delivery_id": "b22e5fa1c3...",
"data": {
"history_id": "...",
"topic": "5 productivity myths",
"project_folder": "my-project",
"error": "Composition Slidereel not found"
}
}{
"event": "test.ping",
"timestamp": 1746288000,
"delivery_id": "c91d3e...",
"data": {
"message": "Hello from Slidereel",
"sent_by": "test_button"
}
}Headers
Every request includes:
Content-Type: application/jsonX-Slidereel-Event— the event name (also in the body).X-Slidereel-Delivery— the unique delivery id.X-Slidereel-Signature— see below.User-Agent: Slidereel-Webhook/1.0
Signature verification
X-Slidereel-Signature has the format:
X-Slidereel-Signature: t=1746288000,v1=<hex hmac sha256>Compute the expected signature as HMAC_SHA256(secret, "${timestamp}.${raw_body}"). Compare with constant-time equality. Reject if the timestamp is more than 5 minutes from now (replay protection).
// Node — verify X-Slidereel-Signature
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, body, header) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const t = parseInt(parts.t, 10);
const sig = parts.v1;
if (!t || !sig) return false;
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${body}`)
.digest("hex");
if (expected.length !== sig.length) return false;
return timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(sig, "hex"),
);
}# Python — verify X-Slidereel-Signature
import hmac, hashlib, time
def verify(secret: str, body: str, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
sig = parts.get("v1", "")
if abs(time.time() - t) > 300:
return False
expected = hmac.new(
secret.encode(), f"{t}.{body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig)Delivery & reliability
- Timeout: we wait up to 10 seconds for your endpoint to respond. Return any
2xxstatus to acknowledge. - Retries: v1 is single-attempt. Deliveries that fail (non-2xx, timeout, connection error) are logged but not retried server-side. If reliability is critical, queue inside your handler and ack within the timeout.
- Auto-disable: after 10 consecutive failed deliveries, we automatically disable the webhook. You'll see this in the Billing page card and can re-enable after fixing your endpoint.
- Order: events are not strictly ordered. Use
timestampif order matters. - Idempotency: we may retry in future versions. Always dedupe by
delivery_id.
Best practices
- Respond fast. Return 200 immediately, then process asynchronously. Anything past 10 seconds counts as a failure and counts toward auto-disable.
- Verify the signature on every request. Without it, anyone who knows your URL can post fake events.
- Treat the secret like an API key. Store in environment variables. Rotate via the Billing page if exposed.
- Save
delivery_idbefore processing. Reject duplicates so future retries don't double-act. - Download
video_urlpromptly. Signed URLs expire. Copy the file to your own storage if you need long-term access.
Troubleshooting
- "Webhook URL resolves to a private network address" — the hostname resolves to 10/8, 172.16/12, 192.168/16, 169.254/16, loopback, or IPv6 ULA. Use a publicly routable hostname.
- Auto-disabled banner showing — your endpoint failed 10 times in a row. Fix it, re-enable the toggle, and click Save (the failure counter resets on save).
- Test event returns 401/403 — your endpoint is rejecting unknown callers. Allowlist Slidereel by signature verification, not source IP (we don't publish a stable IP range).
- Test event times out — your endpoint took longer than 10s. Reduce the synchronous work in your handler.