call.recording.ready
Fires when a call recording has been processed and is available for download.
Status: This event will start emitting once gateway-go upstream signals land. The contract documented here is stable.
call.recording.ready fires once the recording pipeline has written and indexed the audio file for a completed call. It carries a presigned URL for download; the URL expires according to your workspace's storage-tier retention policy. The event is routed per-number via voice_callback_url / events_url and is not subscribable as a workspace webhook.
{
"kind": "call.recording.ready",
"event_id": "01900000-0000-7000-8000-000000000001",
"workspace_id": "01900000-0000-7000-8000-000000000002",
"occurred_at": "2026-06-27T10:06:00.000Z",
"data": {
"call_id": "01900000-0000-7000-8000-000000000003",
"number_id": "01900000-0000-7000-8000-000000000004",
"recording_id": "01900000-0000-7000-8000-000000000005",
"duration_seconds": 295,
"size_bytes": 2457600,
"download_url": "https://storage.example.com/recordings/...?X-Amz-Expires=3600",
"expires_at": "2026-06-27T11:06:00.000Z"
}
}Every webhook delivery includes the following request headers:
| Header | Description |
|---|---|
X-Sautikit-Signature | HMAC-SHA256 of the raw body, hex-encoded. Verify with your subscription secret. |
X-Sautikit-Idempotency-Key | Unique delivery ID for deduplication. |
X-Sautikit-Event | Literal event kind: call.recording.ready. |
event_id or the X-Sautikit-Idempotency-Key header.dead_letter.import { createHmac } from "node:crypto";
export async function POST(req) {
const sig = req.headers["x-sautikit-signature"];
const body = await req.text();
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(body)
.digest("hex");
if (sig !== expected) return new Response("Forbidden", { status: 403 });
const event = JSON.parse(body);
if (event.kind === "call.recording.ready") {
const { call_id, download_url, expires_at } = event.data;
// download before expires_at, then store in your own bucket
console.log(`Recording for call ${call_id} ready: ${download_url} (expires ${expires_at})`);
}
return new Response("OK", { status: 200 });
}voice_callback_url / events_url on the Numbers Routing tab.