CastFork
Developers

Have CastFork call your server, not the other way around

Signed webhooks are live today: stream started, stream ended, a destination failing mid-broadcast, a recording finishing. A public REST API is planned but isn’t built yet — we’d rather say that plainly than list endpoints that don’t exist.

Webhooks · available nowPublic REST API · coming soon
Available now

Signed webhooks for the events that actually matter

An enhancement beyond the streaming platform we're built on parity with — theirs has no webhook system at all. HMAC-signed, retried on failure, with a full delivery log.

01

Add an endpoint

Settings → Webhooks → Add endpoint. Give it an https:// URL, an optional label, and check the events you want.

02

Save the signing secret

CastFork shows the whsec_… signing secret exactly once on save. Rotate the endpoint to mint a new one if you lose it.

03

Verify the signature

Every delivery carries castfork-signature (t=…,v1=…) — HMAC-SHA256 of the timestamp and raw body, keyed with your secret.

04

Return a 2xx

Anything else, or a timeout past 10 seconds, counts as a failure and gets retried on a backoff schedule.

Event catalog

Four event types, one envelope shape

TypeFires whendata
stream.startedAn ingest goes live and fan-out beginssession, destinations[]
stream.endedA live session closessession (startedAt, endedAt)
destination.failedA destination errors mid-broadcastsession, destination (errorCode)
recording.readyA cloud recording finishes processingsession, recording (id, key, bytes, durationS)

Payloads never carry secrets — no RTMP URL, no stream key. Per-plan endpoint caps: Free 1, Standard 3, Professional 5, Business 10, Enterprise 20.

Verifying a delivery

Constant-time HMAC-SHA256, against the raw body

const crypto = require("node:crypto");

function verifyCastforkWebhook(rawBody, header, secret, toleranceSec = 300) {
  // header: "t=1752345791,v1=abc123…"
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=").map((s) => s.trim())),
  );
  const t = Number(parts.t);
  if (!Number.isFinite(t)) return false;
  if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // stale

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

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
  • 10-second delivery timeout, retried up to 5 times: 1m, 5m, 30m, 2h, then 6h
  • The newest 100 deliveries per endpoint are retained for inspection
  • URLs are SSRF-guarded — plain http:// and private/loopback/link-local hosts are rejected

A public REST API is coming, not here yet

Soon

We’re planning a public API covering channels, stream cards, and events — mirroring the same shapes the CastFork app itself uses internally. Until it ships, webhooks are the supported way to react to what’s happening on your account from your own code.

Developer FAQ

Questions about building against CastFork

Webhooks

Key your handler off the castfork-delivery-id header (or the envelope's id field) so a retried delivery is a no-op the second time it arrives.

You can't view it again after the initial save — rotate the endpoint from Settings → Webhooks to mint a new one.

Yes — the Send test button fires a webhook.test event through the same envelope and signing scheme as real events, so you can verify your handler before relying on it.

API roadmap

We don't have a date to publish yet. When it ships, this page will list real endpoints instead of a roadmap note.

Not yet. It's a reasonable idea for later, but nothing here works today — we'd rather leave it off this page than list it as available.

Start with webhooks, plug in the API once it ships

Free covers two destinations at once, no time limit, no card required.