call.completed
The last event of an answered call, POSTed to the number's events_url with the call's duration.
The last event of an answered call, POSTed to the number's events_url with the call's duration.
call.completed is the last event of a call that was answered. Its callSessionState is Completed and its status is Success. A call that ends without being answered has a different status: see call.failed.
Call events are POSTed to the events_url on the number's routing, which you set with PUT /v1/numbers/{id}/routing. They are not workspace webhooks: you cannot subscribe to them with POST /v1/webhooks.
The body is flat JSON.
{
"callSessionState": "Completed",
"direction": "Inbound",
"sessionId": "HD_24d73b4518bc",
"callerNumber": "+254720000000",
"clientDialedNumber": "+254720000001",
"destinationNumber": "+254720000001",
"callStartTime": "2026-06-27+10:00:00",
"durationInSeconds": "295",
"status": "Success",
"isActive": "0"
}| Field | Type | Description |
|---|---|---|
callSessionState | string | Completed for this event. |
direction | string | Inbound or Outbound. |
sessionId | string | The call's id, starting HD_. The same on every event of the call. GET /v1/calls/{id} accepts it. |
callerNumber | string | Who is calling. On an inbound call, the caller's number. |
clientDialedNumber | string | The number that was dialled. On an outbound call, the person you called. |
destinationNumber | string | Your Sautikit number, on inbound and outbound calls. |
callStartTime | string | When the call started. |
isActive | string | "1" while the call is live, "0" once it has ended. |
status | string | Success for a call that was answered and then ended. |
durationInSeconds | string | Length of the call in seconds, as a string. |
dialDurationInSeconds | string | When the call used a dial action: seconds the dialled party was connected. |
recordingUrl | string | Present when the call was recorded. See call.recording.ready. |
Other fields may be present. The price of the call is not in the event: read cost_minor from GET /v1/calls/{id} a few seconds later.
events_url one time and is not retried. Respond within 5 seconds; the response body is ignored.X-Sautikit-* headers and no signature. Use an events_url with a path that is hard to guess, and confirm anything that matters with GET /v1/calls/{id}, passing the sessionId.callSessionState says which one it is.import express from "express";
const app = express();
app.use(express.json());
app.post("/sautikit/events", (req, res) => {
res.sendStatus(200); // answer first, then do the work
const event = req.body;
if (event.callSessionState === "Completed" && event.status === "Success") {
console.log(`Call ${event.sessionId} completed: ${event.durationInSeconds}s`);
// close the call in your records, start your after-call work
}
});events_url with PUT /v1/numbers/{id}/routing.GET /v1/calls/{id}.