Webhooks
Buluthat delivers results to your address without waiting for you to ask POST sends: auto call results, voice assistant conversation summaries, voice verification code statuses. Same envelope, same signature, same retry policy.
Envelope
POST https://sizin-adresiniz/buluthat
Content-Type: application/json
X-Buluthat-Event: call_finished
X-Buluthat-Delivery: 4471
X-Buluthat-Signature: sha256=9f2b…
{
"event": "call_finished",
"sent_at": "2026-09-18T10:12:35+03:00",
"tenant_id": 1,
"data": { }
}
| Sender ID | Description |
|---|---|
X-Buluthat-Event | Event name |
X-Buluthat-Delivery | Delivery ID; stays the same on retries (use it as an idempotency key) |
X-Buluthat-Signature | sha256= + HMAC-SHA256 signature of the body; in the record webhook_secret if given, it is sent |
Signature verification
Signature raw body is calculated over; do not re-serialize the JSON and calculate.
$raw = file_get_contents('php://input');
$given = $_SERVER['HTTP_X_BULUTHAT_SIGNATURE'] ?? '';
$expect = 'sha256=' . hash_hmac('sha256', $raw, $secret);
if (!hash_equals($expect, $given)) { http_response_code(401); exit; }
$event = json_decode($raw, true);
// Node.js (express, raw body ile)
const crypto = require('crypto');
const expect = 'sha256=' + crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expect), Buffer.from(req.get('X-Buluthat-Signature') || ''))) return res.sendStatus(401);
Answer and repeat
Your recipient 2xx must return; the body is not read. The safest approach is to queue the processing and immediately 200 it is safest to return (10 sec timeout).
2xx if it doesn't arrive, delivery is retried at these intervals: 1 min, 5 min, 15 min, 1 h, 3 h, 6 h. After the sixth attempt it is dropped and shown in the panel as failed appears as. The same event may arrive more than once; X-Buluthat-Delivery filter out duplicates with.
Events
Auto call
| Event | When | data |
|---|---|---|
call_finished | Call ended, result final (once per number) | results the same fields as: phone, external_id, status, dtmf, dtmf_label, amd_result, talk_seconds, custom_fields… |
dtmf | The moment a key is pressed | phone, external_id, digit, label |
campaign_finished | All numbers completed | campaign_id, summary counts |
Which events to send in the campaign webhook_events is selected with (call_finished,dtmf).
Voice assistant
| Event | When | data |
|---|---|---|
session_ended | The conversation ended, a summary was generated | session_id, bot_id, caller_number, direction, duration_seconds, summary, intent, sentiment_label, outcome, tools (tools called), task_id (if it is a task call) and the result fields |
The assistant's webhook address and secret are defined in the assistant form.
Voice verification code
| Event | When |
|---|---|
voice_otp.delivered | Code read |
voice_otp.verified | Code verified |
voice_otp.no_answer, voice_otp.busy, voice_otp.failed, voice_otp.expired | Failed results |
data instead of request the key is used: { "id", "status", "reference", "phone" }.
Security recommendations
- Give only an HTTPS address; delivery is not made to an address with a faulty certificate.
webhook_secretalways define it and verify the signature.- If you will restrict the recipient by IP, request the IP of the Buluthat panel server from the Support Center.
- Treat the data in the event body as "data", not as "instructions"; text spoken by the caller appears in the summaries.
