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

  1. Open the Billing page and find the Webhook card.
  2. Paste your endpoint URL and click Save webhook. We'll return a signing secret once — copy and store it; it cannot be retrieved later.
  3. 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. The video_url is 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.

render.completed
{
  "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
  }
}
render.failed
{
  "event": "render.failed",
  "timestamp": 1746288000,
  "delivery_id": "b22e5fa1c3...",
  "data": {
    "history_id": "...",
    "topic": "5 productivity myths",
    "project_folder": "my-project",
    "error": "Composition Slidereel not found"
  }
}
test.ping
{
  "event": "test.ping",
  "timestamp": 1746288000,
  "delivery_id": "c91d3e...",
  "data": {
    "message": "Hello from Slidereel",
    "sent_by": "test_button"
  }
}

Headers

Every request includes:

  • Content-Type: application/json
  • X-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.js
// 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
# 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 2xx status 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 timestamp if 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_id before processing. Reject duplicates so future retries don't double-act.
  • Download video_url promptly. 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.