If you already run programmable voice on Africa's Talking (AT) and want to move it to Sautikit, this guide is the short version. Five steps: claim a number, swap the auth header, keep your voice XML (Sautikit accepts XML) and adjust a few verb differences, update webhook signature verification, and point your outbound calls at the new endpoint. Most teams finish a basic move in an afternoon.
Instant, self-serve numbers. A Sautikit number is KES 100/month ex VAT (KES 116 incl.), claimed instantly from the dashboard. AT's route to a DID starts around KES 5,000 to get the number plus KES 2,500/month incl. tax.
Keep your voice XML, or go JSON. Sautikit accepts your existing voice XML as-is, and also offers a JSON DSL if you'd rather generate call flows from code and a database with typed, validated actions.
M-Pesa STK push top-up. Fund your wallet in about 30 seconds: your phone rings with the PIN prompt, no Paybill menu.
Per-second billing. Outbound is KES 0.05/sec billed per second after connect; inbound is free; 5 GB of recording storage is included.
None of this makes AT a bad platform; it is multi-channel and broad. If you only need voice in Kenya, Sautikit is the narrower, cheaper-to-start path.
Good news: this step is small. Sautikit accepts XML voice actions directly (it forwards the raw XML to the telephony engine), so your existing AT <Response> flows keep working. You don't port or rewrite anything to move over; you only adjust a couple of small verb differences.
Here is a "say a prompt, collect one digit" flow in Sautikit's XML on the left and the same flow in JSON on the right, both accepted at runtime. Two things change versus AT: the prompt <Say>nests inside<GetDigits> rather than sitting beside it, and the digit-count and callback attributes are maxDigits/action rather than AT's numDigits/callbackUrl. The JSON column is optional: reach for it only if you want Sautikit-side validation and typed SDKs; otherwise keep returning your XML.
{"actions": [ { "say": { "text": "Press 1 for account balance, or 2 to speak with an agent.", "voice": "alice", "language": "en-KE" } }, { "getDigits": { "numDigits": 1, "timeout": 5, "finishOnKey": "#", "nested": [ { "say": { "text": "Press 1 for balance, 2 for an agent." } } ] } }]}
<Response><Say voice="alice" language="en-KE">Press 1 for account balance, or 2 to speak with an agent.</Say><GetDigits maxDigits="1" timeout="5" finishOnKey="#"> <Say>Press 1 for balance, 2 for an agent.</Say></GetDigits></Response>
You can keep returning your XML from this handler unchanged. If you opt into the JSON DSL instead, your handler returns the JSON object:
// Express: your voice_callback_url handlerapp.post("/voice", (req, res) => { res.json({ actions: [ { say: { text: "Press 1 for account balance, or 2 to speak with an agent.", language: "en-KE" } }, { getDigits: { numDigits: 1, timeout: 5, finishOnKey: "#", nested: [{ say: { text: "Press 1 for balance, 2 for an agent." } }], }, }, ], });});
When the caller enters a digit, Sautikit POSTs back to your callback URL with a Digits field. The Voice Actions reference lists every verb (say, play, getDigits, dial, record, redirect, reject, hangup, conference, stream) in both XML and JSON; most map one-to-one from their AT XML equivalents.
Swap AT's verification for Sautikit's. Sautikit signs each webhook with HMAC-SHA256 over body + "." + timestamp, delivered in the X-Sautikit-Signature: t=<ts>,v1=<hex> header:
Sautikit is voice-only by design, so if your AT integration also uses SMS, USSD, WhatsApp, or airtime, those channels do not move to Sautikit; they move to Helloduty, the multi-channel CX platform Sautikit belongs to. Voice runs on Sautikit; SMS, WhatsApp, USSD, and the agent desk run on Helloduty, in the same family and the same billing relationship. So a full AT migration is usually voice → Sautikit and everything else → Helloduty, not a single swap.