Voice OTP

Deliver a verification code by phone call instead of SMS. We generate the code, read it aloud digit by digit, and verify it through the same endpoint you already use.

It is the same endpoint as an SMS code with one extra field. Everything else — verification, expiry, attempt limits — is unchanged, so adding it as a fallback is a one-line change.

POST/api/v1/otp/send
Parameters
medium
string
sms (default) or voice. With voice we call the number and read the code aloud.
phone
stringrequired
The number to verify, as 024… or +233….
sender
stringrequired
Your approved sender ID. On a voice call it is spoken as the business name — "your verification code from Acme is…".
length
integer
Code length, 4 to 10. Defaults to 6.
expiry_minutes
integer
1 to 30. Defaults to 5.
max_attempts
integer
Wrong guesses allowed before the code is burned.
Shell
curl -X POST https://api.jkasms.com/api/v1/otp/send \
  -H "Authorization: Bearer $JKASMS_SECRET_KEY" \
  -H "X-JKASMS-Org-Id: $JKASMS_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "0241234567",
    "sender": "ACME",
    "medium": "voice"
  }'
Response202
JSON
{
  "success": true,
  "data": {
    "request_id": "otp_3mK9xQ2pLd",
    "phone": "+233241234567",
    "expires_at": "2026-09-27T09:35:00.000Z",
    "medium": "voice",
    "segments": 0,
    "cost": "0.00",
    "currency": "GHS",
    "voice_seconds": 14
  }
}

Note what is absent: the code itself. It exists only in the call and as a hash in our database. Neither the speech engine nor the carrier is told what it means — they receive audio, not a secret they could verify on your behalf.

What it costs

voice_seconds is the call time charged, and it comes out of your voice credit — not your SMS credits and not your wallet. A six-digit code read twice at a deliberate pace is usually 12 to 18 seconds.

segments is 0 and cost is 0.00 on the voice path. No SMS was sent.

With no voice credit the request is refused with 402 INSUFFICIENT_BALANCE, even if you hold thousands of SMS credits. It never silently falls back to SMS: a caller who asked for a call should not get a text, and the two bill from different balances.

Verifying

Identical to the SMS path — the medium does not change verification.

POST/api/v1/otp/verify
Shell
curl -X POST https://api.jkasms.com/api/v1/otp/verify \
  -H "Authorization: Bearer $JKASMS_SECRET_KEY" \
  -H "X-JKASMS-Org-Id: $JKASMS_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{ "request_id": "otp_3mK9xQ2pLd", "code": "482913" }'

How the code is read out

Digit by digit, with a pause, then repeated. Engines read 482913 as “four hundred and eighty-two thousand, nine hundred and thirteen” unless told otherwise, which nobody can write down — so we spell it out and slow the delivery. The code is announced twice, because a code heard once is often a code missed.

Using it as an SMS fallback

This is what voice OTP is best at. When an SMS code goes undelivered — a blocked handset, a congested network, a recipient who cannot read a screen — retry the same flow with medium: "voice" rather than sending the text again.

JAVASCRIPT
async function sendCode(phone, medium = "sms") {
  const res = await fetch("https://api.jkasms.com/api/v1/otp/send", {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.JKASMS_SECRET_KEY}`,
      "x-jkasms-org-id": process.env.JKASMS_ORG_ID,
      "content-type": "application/json",
    },
    body: JSON.stringify({ phone, sender: "ACME", medium }),
  });
  return res.json();
}

// The user says the text never arrived. Call them instead — same flow,
// same verify endpoint, a different balance.
const retry = await sendCode(phone, "voice");
An OTP call is never re-dialled. The code expires, so a second call minutes later would read out a code that no longer works — which reads as a broken product, or worse, as a scam.

Test keys

A TEST key does not place a real call and spends no credit. Voice OTP requires a live voice route, so in test mode use medium: "sms" to exercise the flow — the verification half is identical either way. See Test mode.