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"
}
FieldRequiredDescription
phoneyesNumber to call
codenoYour own code; if empty, Buluthat generates it
lengthnoNumber of code digits to generate (4-8, default 6)
referencenoYour own recording; status/list to find with
caller_id, trunk_slugnoCaller number and line
repeatnoHow many times the code is read (1-5, default 2)
ttl_minutesnoCode validity (at most 60, default 5)
company_namenoCompany name in the greeting announcement; the account name if empty
webhook_url, webhook_secretnoStatus 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: 422 and error: 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.