Voice Verification (OTP)
The "we call you and read the code" model: you provide the number, the PBX calls and reads the verification code digit by digit to whoever answers (press 1 to repeat), then hangs up. You can send the code yourself, or Buluthat can generate it and return it once in the response. The code is stored in the database only as hash is stored as; status does not appear in the response.
Endpoint: https://api.buluthat.com/api/voice_otp.php — Integration key (bt_…, scope voice_otp / autocall / voicebot / *). In the account voice_otp the module must be on.
POSTsend
{
"action": "send",
"phone": "05551112233",
"code": "482913",
"length": 6,
"reference": "CARI-451",
"caller_id": "02124119610",
"trunk_slug": "hat-1",
"repeat": 2,
"ttl_minutes": 5,
"company_name": "Byfix",
"webhook_url": "https://crm.example.com/otp-sonuc.php",
"webhook_secret": "gizli"
}
| Field | Required | Description |
|---|---|---|
phone | yes | Number to call |
code | no | Your own code; if empty, Buluthat generates it |
length | no | Number of code digits to generate (4-8, default 6) |
reference | no | Your own recording; status/list to find with |
caller_id, trunk_slug | no | Caller number and line |
repeat | no | How many times the code is read (1-5, default 2) |
ttl_minutes | no | Code validity (at most 60, default 5) |
company_name | no | Company name in the greeting announcement; the account name if empty |
webhook_url, webhook_secret | no | Status notification |
Response:
{ "ok": true, "code": "482913", "data": { "id": 17, "status": "calling", "expires_at": "2026-09-18 10:17:03" } }
code returned only when Buluthat generated it; if you sent it null.
Errors (422): invalid number, more than 3 calls to the same number within 10 minutes (rate_limited), daily cap (daily_limit), ongoing call (in_progress), no line, no voice synthesis key, the PBX could not place the call (the reason is in the message).
POSTverify
{ "action": "verify", "id": 17, "code": "482913" }
id instead of phone (+ reference) can also be given; the latest record opened for that number is used.
- Correct:
{ "ok": true, "verified": true } - Wrong:
422anderror:wrong_code(remaining trials in the message),expired,too_many_attempts(5),not_delivered(call was not placed),not_found
If the call was opened, the code (answered/delivered) can be verified — even if the person hangs up after hearing the code.
GETstatus
GET https://api.buluthat.com/api/voice_otp.php?action=status&id=17
Statuses: pending → calling → answered → delivered → verified; failures no_answer, busy, failed, expired. final: true if, the call has ended. Poll every 2-3 seconds or use a webhook.
GETlist
GET ?action=list&phone=0555…&reference=CARI-451&limit=20
GETcaller_ids · trunks
Caller number and line options (same as the auto-call API).
Webhook
webhook_url if given, on status changes (delivered, verified, no_answer, busy, failed, expired) is sent via POST:
{ "event": "voice_otp.delivered", "request": { "id": 17, "status": "delivered", "reference": "CARI-451", "phone": "05551112233" } }
Headers X-Buluthat-Event, X-Buluthat-Delivery, webhook_secret if given X-Buluthat-Signature: sha256=<hmac>. For a non-2xx response it is retried after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h.
PHP client
Downloaded from the panel buluthat-voice-otp-client.php:
require 'buluthat-voice-otp-client.php';
$otp = new BuluthatVoiceOtp('https://api.buluthat.com', 'bt_xxx');
$r = $otp->send('05551112233', ['reference' => 'CARI-451']); // $r['code'], $r['data']['id']
// ... kullanıcı kodu girer ...
$v = $otp->verify($r['data']['id'], $girilenKod); // $v['ok'] === true
Why instead of SMS?
- Does not require İYS consent or an SMS sender ID, and also works on a landline.
- No undelivered-SMS problem: you know whether the call was answered and whether the code was read.
- For elderly or visually impaired users, listening to the code is easier than reading text.
- Billing applies only to answered calls, according to your package rules.
