call.recording.ready
How a call recording reaches you: a recordingUrl on the call's final event, POSTed to the number's events_url.
How a call recording reaches you: a recordingUrl on the call's final event, POSTed to the number's events_url.
When a recorded call ends, the link to the audio arrives in the recordingUrl field of the call's last event (callSessionState: "Completed"). There is no separate request for the recording.
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",
"recordingUrl": "https://storage.sautikit.com/...",
"status": "Success",
"isActive": "0"
}| Field | Type | Description |
|---|---|---|
callSessionState | string | Completed. |
sessionId | string | The call's id, starting HD_. |
recordingUrl | string | A link to the recording. Present only when the call was recorded. |
durationInSeconds | string | Length of the call in seconds, as a string. |
The other fields are the same as on call.completed. Other fields may be present.
The link expires, so download the file to your own storage or keep the sessionId and ask for a fresh link when you need one:
const res = await fetch(
`https://api.sautikit.com/v1/calls/${sessionId}/recording`,
{ headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` } }
);
const recording = await res.json();200 with status: "ready": url is a download link valid for 15 minutes.202 with status: "pending": the file is still being saved. Try again after retry_after_seconds.410: the recording passed your storage plan's retention period and was deleted.Recording a call is free. Recordings use your storage: 100 MB free, kept 24 hours, with larger sizes on pricing.
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.recordingUrl) {
console.log(`Recording for ${event.sessionId}: ${event.recordingUrl}`);
// download the file to your own storage
}
});record action.call.recording.available (see Webhook concepts).