Collections teams in Kenya lose most of their recovery margin to two things: the cost of human agents dialing one number at a time, and borrowers who never see an SMS buried under fifty others. A debt-reminder robocall fixes both. One campaign dials your whole defaulter list, plays a bilingual message, and lets the borrower press 1 to pay there and then via M-Pesa: no agent, no app, no callback.
This guide builds that flow end to end on Sautikit, Kenya's voice-only API: the call, the press-to-pay branch wired to M-Pesa STK push, promise-to-pay capture, an escalation ladder, and, critically, the CBK and Data Protection Act guardrails a Kenyan collections team must respect. Every code block runs against the real /v1/calls API.
TL;DR
A robocall + getDigits "press 1 to pay" branch can trigger an M-Pesa STK push mid-call, so the borrower repays without hanging up.
At KES 3/min outbound, billed per second from the moment the call connects, a 25-second reminder call costs about KES 1.25, a fraction of an agent dial.
In Kenya, debt-collection harassment became a criminal offence on 1 January 2025; contacting a borrower's phone contacts is prohibited under the CBK Digital Credit Providers Regulations. Build consent and quiet-hours into the campaign, not as an afterthought.
SMS is cheap, but for recovery it is weak: it competes with promotional clutter, it is easy to ignore, and it cannot close: the borrower still has to leave the inbox, open M-Pesa, and remember your paybill. A voice call does three things SMS cannot:
It commands attention. A ringing phone gets answered; a text gets archived. Voice reaches feature-phone and low-literacy borrowers that app-based nudges miss entirely.
It closes in-session. Press 1, and an STK push lands on the same handset while the borrower is still engaged. The decision and the payment happen in one interaction.
It creates an audit trail. A recorded, timestamped interaction is exactly the kind of evidence a CBK-licensed lender wants on file.
This is why voice is a recovery channel, not just a notification channel. SMS still has a place (for the soft, early-DPD nudge), and that is where Helloduty, the multi-channel CX platform Sautikit is part of, fits: SMS and WhatsApp for the gentle reminders, Sautikit voice for the calls that actually collect. More on that ladder below.
A robocall campaign is just a loop over your defaulter list, one POST /v1/calls per borrower. The call is dialed immediately; the response returns before the borrower answers, so you poll GET /v1/calls/{id} or subscribe to the call.answered / call.completed webhooks for the outcome.
// campaign.js: dial one borrowerconst SAUTIKIT_API_KEY = process.env.SAUTIKIT_API_KEY;const FROM_NUMBER = process.env.SAUTIKIT_FROM; // a number your workspace ownsconst BASE = "https://api.sautikit.com";async function placeReminder(borrower) { const resp = await fetch(`${BASE}/v1/calls`, { method: "POST", headers: { "Authorization": `Bearer ${SAUTIKIT_API_KEY}`, "Content-Type": "application/json", // Reuse the loan ID so a retry never double-dials the same borrower "Idempotency-Key": `reminder:${borrower.loan_id}:${borrower.due_date}`, }, body: JSON.stringify({ from: FROM_NUMBER, to: [borrower.msisdn] }), }); if (resp.status === 402) { throw new Error("Wallet empty. Top up before running the campaign"); } if (!resp.ok) throw new Error(`Call failed: ${resp.status}`); const { call_id } = await resp.json(); return call_id;}
The Idempotency-Key header matters in collections: campaigns get retried, lists get re-uploaded, and double-dialing a borrower is both annoying and, if it looks like harassment, a compliance risk. Keying on loan_id + due_date guarantees one reminder per cycle.
When the borrower answers, Sautikit POSTs the call state to your voice_callback_url. You respond with a list of voice actions. For a reminder, that is a say (the message) followed by a getDigits (the menu).
Three rules for the script: identify the lender (transparency is a legal requirement, not a courtesy), state the amount and due date plainly, and keep it under 30 seconds. Lead in English, repeat the menu in Swahili.
const express = require("express");const app = express();app.use(express.urlencoded({ extended: true }));app.post("/voice/reminder", (req, res) => { const name = req.query.name || ""; const amount = req.query.amount || ""; // e.g. "2,500" const due = req.query.due || ""; // e.g. "5 July" res.json({ actions: [ { say: { text: `Hello ${name}. This is a payment reminder from Acme Credit. ` + `Your balance of ${amount} shillings is due on ${due}. ` + "To pay now by M-Pesa, press 1. " + "Kulipa sasa kwa M-Pesa, bonyeza moja. " + "To request more time, press 2. Kuomba muda zaidi, bonyeza mbili. " + "To speak to an agent, press 3. Kuongea na wakala, bonyeza tatu.", language: "en-KE", }}, { getDigits: { numDigits: 1, timeout: 8000, finishOnKey: "", action: `${BASE_URL}/voice/reminder-branch?loan_id=${req.query.loan_id}`, }}, // Fallback if the borrower says nothing { say: { text: "We did not receive a selection. Goodbye. Kwaheri.", language: "en-KE" }}, { hangup: {} }, ]});});
If you are coming from an XML-based voice API (Africa's Talking, Twilio/TwiML), the same reminder flow reads naturally as XML. Both forms below work at runtime: Sautikit parses and validates the JSON DSL, and raw XML is forwarded to the telephony engine byte-for-byte, so your existing flows carry over as-is.
{"actions": [ { "say": { "text": "Hello. This is a payment reminder from Acme Credit. Your balance of 2,500 shillings is due on 5 July.", "language": "en-KE" } }, { "getDigits": { "numDigits": 1, "timeout": 8, "action": "https://yourapp.example/voice/reminder-branch", "nested": [ { "say": { "text": "To pay now by M-Pesa, press 1. Kulipa sasa kwa M-Pesa, bonyeza moja." } } ] }}, { "say": { "text": "We did not receive a selection. Goodbye. Kwaheri.", "language": "en-KE" } }, { "hangup": {} }]}
<Response><Say language="en-KE">Hello. This is a payment reminder from Acme Credit. Your balance of 2,500 shillings is due on 5 July.</Say><GetDigits maxDigits="1" timeout="8" action="https://yourapp.example/voice/reminder-branch"> <Say>To pay now by M-Pesa, press 1. Kulipa sasa kwa M-Pesa, bonyeza moja.</Say></GetDigits><Say language="en-KE">We did not receive a selection. Goodbye. Kwaheri.</Say><Hangup /></Response>
Note the two XML verb differences if you are porting muscle memory: the menu prompt <Say> nests inside <GetDigits>, and XML uses maxDigits where JSON uses numDigits.
This is the part that turns a reminder into a collection. When the borrower presses 1, your branch handler fires a Daraja STK push to the same MSISDN, so the M-Pesa PIN prompt appears on their handset while they are still on the call. Respond with JSON actions, or return raw XML if you kept your AT/Twilio-style responses; both tabs below are the same handler:
You do not need to invent stk_push; it is the standard Daraja CustomerPayBillOnline / Lipa na M-Pesa Online call. If you already top up your Sautikit wallet over M-Pesa, you have the same Daraja credentials in hand. (For the wallet side, see the topups flow; for the voice verbs, the voice actions reference.)
Not everyone can pay today. Press 2 records a promise-to-pay (PTP), a date the borrower commits to, and schedules an automated follow-up call for that day. A captured PTP is far more recoverable than a silent miss.
app.post("/voice/promise-to-pay", (req, res) => { res.json({ actions: [ { say: { text: "On what date will you pay? Press the day of the month, then hash.", language: "en-KE", }}, { getDigits: { numDigits: 2, finishOnKey: "#", timeout: 10000, action: `${BASE_URL}/voice/ptp-confirm?loan_id=${req.query.loan_id}`, }}, ]});});
Store the PTP keyed to the loan, then enqueue a follow-up call for that date using the same placeReminder loop. This is also the moment to record the call (a record action) so you keep an audit trail of the commitment.
A robocall is one rung. The recovery sequence that works in Kenya looks like this:
Days past due
Channel
Action
1–7
SMS / WhatsApp (Helloduty)
Soft reminder, balance + due date
8–30
Sautikit voice robocall
Press-1-to-pay via M-Pesa, PTP capture
31–60
Voice + WhatsApp + agent dial
Personalised follow-up, payment plan
60+
Human recovery desk
Negotiated settlement, field follow-up
Sautikit owns the voice rungs. The early SMS nudge and the late agent-desk / predictive-dialer rungs are exactly what Helloduty adds: one platform for the whole ladder, with the recordings and audit trail CBK examiners expect. Start with the voice robocall; graduate to the full ladder when your book grows.
Outbound calls bill at KES 3/min, billed per second from the moment the call connects. A typical reminder call (greeting, amount, menu, press-1 acknowledgement) runs about 25 seconds:
25 seconds × (KES 3 / 60) ≈ KES 1.25 per answered call
For a book of 5,000 reminders a month, that is roughly KES 6,250 in call spend, less than a single collections agent's daily output, and it runs in minutes. See current KES rates and the billing increment for the authoritative numbers.
Harassment is now criminal. The Business Laws (Amendment) Act 2024, in force since 1 January 2025, makes debt-collection harassment a criminal offence. Excessive call frequency, threats, or abusive language can expose your business to prosecution.
Never call the borrower's contacts. The CBK Digital Credit Providers Regulations 2022 prohibit accessing a borrower's phonebook or contacting third parties about the debt. Dial the borrower, and only the borrower.
You need a lawful basis. Reminder calls to your own borrowers can rest on contract / legitimate interest under the Data Protection Act 2019; you do not necessarily need marketing-style consent to remind a customer of their own debt. But you must honour objections and keep records. The ODPC has fined firms up to KES 5 million for misuse of personal data in collections.
Suppress Do-Not-Call numbers and keep your calling to reasonable daytime hours. Kenya's published clock-hour windows are written around SMS; treating 08:00–18:00 as a safe ceiling for voice is best practice, not settled law.
Keep recordings and consent records. They are your defence if a borrower complains.
Can a robocall really take an M-Pesa payment during the call?
Yes. When the borrower presses 1, your webhook fires a Daraja STK push to the same phone number. The M-Pesa PIN prompt appears on their handset while they are still on the line, so they confirm without hanging up or opening an app.
Is it legal to call borrowers about a debt in Kenya?
Reminding your own borrower of their own debt is generally lawful under contract / legitimate interest in the Data Protection Act 2019. What is prohibited is harassment, calling third parties or the borrower's contacts, and abusive language; since January 2025 those can be criminal offences.
How much does a debt-reminder call cost?
About KES 1.25 for a 25-second call, at KES 3/min billed per second from the moment the call connects. A 5,000-borrower monthly campaign costs roughly KES 6,250 in call spend.
Do I need a separate SMS provider for the early reminders?
Sautikit is voice-only by design. For SMS and WhatsApp rungs of the recovery ladder, Helloduty (the platform Sautikit is part of) covers those channels, so you can run the whole sequence from one account.
What happens if the borrower doesn't answer?
The call returns a no-answer or busy status via the call.completed webhook. Retry on those statuses with bounded backoff (never retry an answered call), and respect a per-day attempt cap to stay clear of harassment rules.