call.answered
Fires when the remote party answers the call and the media path is established.
Status: This event will start emitting once gateway-go upstream signals land. The contract documented here is stable.
call.answered fires when the called party answers and the media path becomes active. It is routed per-number via voice_callback_url / events_url and is not subscribable as a workspace webhook; configure it on the Routing tab for the relevant number.
{
"kind": "call.answered",
"event_id": "01900000-0000-7000-8000-000000000001",
"workspace_id": "01900000-0000-7000-8000-000000000002",
"occurred_at": "2026-06-27T10:00:05.000Z",
"data": {
"call_id": "01900000-0000-7000-8000-000000000003",
"direction": "inbound",
"from": "+254700000001",
"to": "+254700000002",
"number_id": "01900000-0000-7000-8000-000000000004",
"status": "answered",
"answered_at": "2026-06-27T10:00:05.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.answered. |
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.answered") {
const { call_id, answered_at } = event.data;
console.log(`Call ${call_id} answered at ${answered_at}`);
}
return new Response("OK", { status: 200 });
}voice_callback_url / events_url on the Numbers Routing tab.call.completed to compute call duration once the call ends.