API

Being called back

Rather than polling to the end, register a callback under Settings and KAIROS will POST to it when an analysis settles. One callback per workspace, https only. You are given a signing secret once, at that moment.

POST https://your-service.example.com/kairos
X-Kairos-Timestamp: 1786291200
X-Kairos-Signature: sha256=3f1c…

{
  "event": "analysis.settled",
  "assetId": "e5f1…",
  "status": "complete",
  "domain": "sport",
  "durationSeconds": 5412.32,
  "timelineUrl": "https://kairosapp.tech/api/assets/e5f1…/timeline",
  "occurredAt": "2026-08-20T14:31:07.221Z"
}

It carries a link to the timeline rather than the timeline itself — a two-hour broadcast is hundreds of segments, and you read them with your own key, so nothing decides on your behalf what you may see.

Verifying it

The signature is an HMAC-SHA256 over <timestamp>.<raw body>. Check the timestamp before you trust the digest: that is what makes a captured delivery useless to replay later.

import { createHmac, timingSafeEqual } from 'node:crypto';

// The RAW body. Parsing it to JSON and re-serialising changes bytes, and the signature is over bytes.
export function verify(rawBody, headers, secret) {
  const timestamp = headers['x-kairos-timestamp'];
  const signature = (headers['x-kairos-signature'] || '').replace(/^sha256=/, '');
  if (!timestamp || !signature) return false;

  // Refuse an old delivery even when its signature is perfect — that is what stops a replay.
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;

  const expected = Buffer.from(createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex'));
  const offered = Buffer.from(signature);
  // Constant time, and the length check first: timingSafeEqual throws on a mismatch rather than failing.
  return expected.length === offered.length && timingSafeEqual(expected, offered);
}

Treat the callback as a nudge, not as the record. It is attempted once. If your service is down, or answers slowly, or the connection drops, that delivery is gone — there is no retry queue. So a correct integration still reads progress, whether on a timer or when the callback arrives.

It can also arrive twice for one analysis. Treat it as “this broadcast has settled” rather than as an event in a sequence, and a duplicate costs you nothing.

Something here wrong or missing? Tell us — it is the kind of bug nobody reports.Back to KAIROS

KAIROS needs one cookie to keep you signed in, and remembers your light/dark choice. May we also measure which adverts bring people here, and which parts of the app get used? Those set a Google advertising cookie and a PostHog one, and refusing changes nothing about the product.

Privacy