An IVR (Interactive Voice Response) system answers an inbound call, plays a spoken menu, collects a key-press from the caller, and routes the call to the correct destination or triggers a follow-up action. With Sautikit you implement this entirely through voice-action JSON returned from your webhook endpoint: no proprietary markup language, no telephony SDK to install.
Your webhook URL is attached to a phone number you claim. When a call arrives, Sautikit POSTs to that URL and executes whatever actions your server returns.
The loop is pure HTTP: each step is a POST from Sautikit to your server, and your server replies with JSON. You can have as many levels in the menu as you need by chaining Redirect and additional GetDigits steps.
Your server owns the menu state. The POST body from Sautikit includes the call SID, the caller's number (From), and the collected digits. Use these to look up session context in your database or cache if you need to track where the caller is in a multi-level tree.
Endpoints you call:
POST /v1/numbers: claim a phone number to attach your webhook.PATCH /v1/numbers/{number_id}: set or update the routing_url (your webhook).GET /v1/calls/{call_sid}: fetch call detail records after the call ends.Voice actions used:
Say: text-to-speech prompt for the menu.GetDigits: collect one or more DTMF digits from the caller.Play: play an audio file instead of synthesised speech (optional).Redirect: send the in-progress call to a different webhook URL.Dial: connect the caller to an agent or external number.Hangup: end the call cleanly.import express from "express";
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Step 1: Sautikit calls this when the number is dialled
app.post("/ivr/welcome", (req, res) => {
res.json({
actions: [
{
getDigits: {
numDigits: 1,
timeout: 5,
finishOnKey: "",
action: "https://yourapp.example.com/ivr/route",
nested: [
{
say: {
text: "Welcome to Acme Corp. Press 1 for sales. Press 2 for support. Press 3 to hear this menu again.",
language: "en-US",
},
},
],
},
},
],
});
});
// Step 2: digit collected
app.post("/ivr/route", (req, res) => {
const digit = req.body.Digits ?? req.body.digits ?? "";
if (digit === "1") {
return res.json({
actions: [
{ say: { text: "Connecting you to sales." } },
{ dial: { number: "+254720000001" } },
],
});
}
if (digit === "2") {
return res.json({
actions: [
{ say: { text: "Connecting you to support." } },
{ dial: { number: "+254720000002" } },
],
});
}
// Fallback: replay menu
return res.json({
actions: [{ redirect: { url: "https://yourapp.example.com/ivr/welcome" } }],
});
});
app.listen(3000);curl -X PATCH "https://api.sautikit.com/v1/numbers/{number_id}" \
-H "Authorization: Bearer $SAUTIKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"routing_url": "https://yourapp.example.com/ivr/welcome"}'Sautikit POSTs the call details to routing_url whenever this number receives an inbound call.
IVR usage is billed per minute for the inbound call leg. A caller who navigates a two-level menu and then connects to an agent for five minutes uses:
Dial connects the caller to an agent, the per-minute rate continues for both the inbound and outbound legs.There is no separate fee for running voice actions or for the number of HTTP round-trips between Sautikit and your webhook.
Keep GetDigits timeouts short (3–5 seconds) to avoid unnecessary billed silence time. If no digit is received before the timeout, Sautikit will proceed to the next action or post to the action URL with an empty Digits field, so you can loop the menu rather than holding the call open.