Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kodisc.com/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are how you avoid polling. When a render reaches a terminal state — completed or failed — Kodisc POSTs a signed JSON payload to a URL you control.

When webhooks fire

A delivery is attempted exactly once per terminal state transition for jobs that have a webhookUrl. Two ways to set one:
  • Default for the key: configure a webhookUrl on the API key in the developer dashboard. Every job enqueued with that key inherits it.
  • Per request: pass webhookUrl in the body of POST /api/v2/render to override the key’s default for that single job. Pass null to opt the job out of webhooks entirely.
If neither is set, no webhook is delivered — you’re expected to poll.

Payload

The body is JSON. Field shapes mirror the GET /api/v2/render/{jobId} response.
{
  "jobId": "clx9f0a1b0000abcd1234efgh",
  "endpoint": "render",
  "status": "completed",
  "createdAt": "2026-04-28T17:14:02.118Z",
  "completedAt": "2026-04-28T17:14:20.539Z",
  "durationMs": 18420,
  "creditsCost": 42,
  "metadata": { "projectId": "proj_42" },
  "result": {
    "video": "https://cdn.kodisc.com/r/clx9f0a1b0000abcd1234efgh/video.mp4",
    "thumbnail": "https://cdn.kodisc.com/r/clx9f0a1b0000abcd1234efgh/thumb.jpg",
    "captions": "https://cdn.kodisc.com/r/clx9f0a1b0000abcd1234efgh/captions.vtt"
  },
  "error": null
}
For failed jobs, status is "failed", result is null, and error carries a human-readable message.

Headers

Each delivery includes:
HeaderValue
content-typeapplication/json
kodisc-eventcompleted or failed
kodisc-signaturet=<unix-seconds>,v1=<hex hmac> — see below

Verifying signatures

The signature lives in kodisc-signature and looks like t=1714326842,v1=4f8a…. To verify:
  1. Split the header on , and parse out t (timestamp) and v1 (HMAC).
  2. Build the signed string as ${t}.${rawBody} — the literal request body, byte-for-byte, before any JSON parsing.
  3. Compute HMAC-SHA256(signed_string, webhook_secret) and hex-encode it.
  4. Compare against v1 using a constant-time equality check.
Reject the request if anything doesn’t match.
import crypto from "node:crypto";

export function verifyKodiscSignature(
  rawBody: string,
  header: string | null,
  secret: string,
): boolean {
  if (!header) return false;

  const parts = Object.fromEntries(
    header.split(",").map((p) => p.trim().split("=") as [string, string]),
  );
  const timestamp = parts.t;
  const received = parts.v1;
  if (!timestamp || !received) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(received, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Verify against the raw request body, not a re-serialized version. Frameworks like Express or FastAPI may JSON-parse the body and lose key ordering — capture the bytes before that happens.

Retries

Kodisc treats a delivery as successful when your endpoint returns a 2xx within 10 seconds. Otherwise, it retries up to 3 more times with these delays from the original attempt:
AttemptDelay before sending
1immediate
230 seconds
35 minutes
430 minutes
After the fourth attempt, Kodisc stops trying. The job’s last delivery status (HTTP code, or 0 if the request never connected) and number of attempts are visible in the dashboard, so you can replay manually if needed. Design your handler to be idempotent — even though Kodisc doesn’t intentionally double-deliver, network edges happen. Treat jobId as the dedupe key. Return 2xx as soon as you’ve persisted the event. Do the heavy work (downloading the video, updating your DB, notifying users) asynchronously. Slow handlers eat into your 10-second window and can trigger spurious retries.

Testing locally

Kodisc requires webhook URLs to start with https://, and deliveries are made from Kodisc’s servers — so http://localhost won’t work. The solution is a tunnel: a tool that gives your local server a public HTTPS URL that Kodisc can reach.

Option 1: ngrok

The most widely used option. Install ngrok, then run:
ngrok http 3000
ngrok prints a URL like https://abc123.ngrok-free.app. Use that as your webhook URL when calling the API or configuring your key.

Option 2: Cloudflare Tunnel

No account required for quick tunnels. Install cloudflared, then run:
cloudflared tunnel --url http://localhost:3000
Cloudflare prints a https:// URL you can use immediately.

Option 3: VS Code port forwarding

If you’re using VS Code with a GitHub account, open the Ports panel, right-click your local port, and select Make Public. VS Code copies an https:// forwarding URL to your clipboard.
Once your tunnel is running, pass its URL as webhookUrl in your render request:
curl -X POST https://api.kodisc.com/api/v2/render \
  -H "Authorization: Bearer <key>" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "...",
    "className": "MyScene",
    "webhookUrl": "https://abc123.ngrok-free.app/webhook"
  }'
Your local handler will receive the delivery as if it were in production.