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.