
VOIS Ses API
Santralinizi kendi yazılımınızdan kullanın: CRM'de müşteri kartına tıklayınca masadaki telefon çalsın ve müşteri aransın, gelen çağrıda yazılımınız arayanı göstersin, çağrı bitince süre ve kayıt yazılımınıza düşsün.
https://api.vois.com.tr/v1/voiceGenel bilgiler
- İstekler HTTPS ile yapılır; POST gövdeleri JSON'dur (
Content-Type: application/json). - Başarılı yanıtlar
{"data": …, "meta": {…}}, hatalar{"error": "<kod>", "message": "<açıklama>"}biçimindedir. - Zamanlar ISO 8601 biçiminde, Türkiye saatiyle döner:
2026-11-12T01:17:50+03:00. - Her çağrının tek bir kimliği vardır:
call_id. Tıkla-ara yanıtında, webhook olaylarında, çağrı geçmişinde ve kayıt indirmede aynı değerdir. - İstek sınırı: API anahtarı başına dakikada 60 istek; tıkla-ara için ayrıca hesap başına dakikada 20 arama, günde 500 arama (VOIS değiştirebilir) ve aynı anda en fazla 5 API çağrısı.
Ses API'sini açtırma
Ses API'si hesap bazında VOIS tarafından açılır. Kullanmak istiyorsanız müşteri temsilcinizle iletişime geçin.
Açık olmayan bir hesapta tüm ses uçları 403 voice_not_enabled döner. Yurt dışı ve 0900 numaraları aramak varsayılan olarak kapalıdır; ihtiyacınız varsa açtırmak için yine bize ulaşın.
API anahtarı ve yetkiler
Anahtarı VOIS panelinizden kendiniz oluşturursunuz: Ayarlar › API & Webhook.
Her istekte anahtar şu başlıklardan biriyle gönderilir:
Authorization: Bearer vois_1234_0123456789abcdef0123456789abcdef01234567
# ya da
X-API-Key: vois_1234_0123456789abcdef0123456789abcdef01234567Anahtarı oluştururken ihtiyacınız olan yetkileri işaretleyin:
| Yetki | İzin verdiği |
|---|---|
| voice:read | Dahililer, süren çağrılar, çağrı geçmişi |
| voice:call | Tıkla-ara ve çağrı kapatma |
| voice:recordings | Ses kaydı indirme |
Hızlı başlangıç
101 numaralı dahiliyi çaldır, açınca 0532 123 45 67'yi ara.
curl -X POST https://api.vois.com.tr/v1/voice/calls \ -H 'Authorization: Bearer ANAHTAR' \ -H 'Content-Type: application/json' \ -d '{"extension":"101","number":"05321234567","reference":"musteri-4521","auto_answer":true}'
<?php $ch = curl_init('https://api.vois.com.tr/v1/voice/calls'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ANAHTAR', 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['extension' => '101', 'number' => '05321234567', 'reference' => 'musteri-4521']), CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true); echo $res['data']['call_id'] ?? $res['error'];
import requests r = requests.post("https://api.vois.com.tr/v1/voice/calls", headers={"Authorization": "Bearer ANAHTAR"}, json={"extension": "101", "number": "05321234567", "reference": "musteri-4521"}, timeout=15) print(r.status_code, r.json())
var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer ANAHTAR"); var res = await http.PostAsync("https://api.vois.com.tr/v1/voice/calls", new StringContent("{\"extension\":\"101\",\"number\":\"05321234567\"}", Encoding.UTF8, "application/json")); Console.WriteLine(await res.Content.ReadAsStringAsync());
const res = await fetch("https://api.vois.com.tr/v1/voice/calls", { method: "POST", headers: { Authorization: "Bearer ANAHTAR", "Content-Type": "application/json" }, body: JSON.stringify({ extension: "101", number: "05321234567", reference: "musteri-4521" }), }); console.log(res.status, await res.json());
Tıkla-ara
Önce dahili çalar (ya da auto_answer ile kendiliğinden açılır); dahili açınca numara, santralinizin normal giden arama kurallarıyla aranır (hat seçimi, arayan numara, kayıt ve çağrı geçmişi masadaki telefondan aranmış gibi işler).
| Alan | Açıklama |
|---|---|
| extension string zorunlu | Çalacak dahili. Telefonu santrale kayıtlı (bağlı) olmalı. |
| number string zorunlu | Aranacak numara. 05321234567, +905321234567, 0532 123 45 67 biçimlerinin hepsi kabul edilir. Kendi dahilileriniz de aranabilir. Bkz. aranabilen numaralar. |
| caller_id_number string opsiyonel | Karşı tarafta görünecek numara. Yalnız hesabınızdaki hat numaralarından biri olabilir; verilmezse dahilinin normal kuralı uygulanır. |
| auto_answer bool opsiyonel | true ise dahilinin telefonu çalmadan kendiliğinden açılır (hoparlör/kulaklık) ve kullanıcı hemen karşı tarafın çalma sesini duyar — "karta bas, müşteri çalsın" deneyimi. Güvenlik nedeniyle hesap bazında VOIS tarafından açılır (açık değilse 403 auto_answer_not_allowed). Telefonda otomatik cevap/intercom izni de açık olmalıdır (Yealink, Grandstream, Fanvil destekler). Varsayılan false. |
| reference string opsiyonel | Kendi sisteminizdeki kayıt numarası (≤64; harf, rakam, . _ : -). Bu çağrının tüm webhook olaylarında ve çağrı geçmişinde geri döner. |
{
"extension": "101",
"number": "05321234567",
"reference": "musteri-4521"
}{
"data": {
"call_id": "9aeba619-e656-4afd-aacc-ba64dcf24aef",
"status": "ringing_extension"
},
"meta": {}
}202 çağrının başlatıldığını gösterir. Dahili 30 saniye içinde açılmazsa çağrı düşer; sonucu call.ended olayından ya da çağrı geçmişinden öğrenirsiniz.
Çağrıyı kapat
Süren bir çağrıyı kapatır (voice:call). Yanıt {"data":{"hung_up":true}}; çağrı yoksa ya da hesabınıza ait değilse 404.
Süren çağrılar
{ "data": [{
"call_id": "9aeba619-…", "direction": "outbound",
"from": "101", "to": "05321234567", "extension": "101",
"state": "ACTIVE", "started_at": "2026-11-12T01:17:50+03:00"
}] }state: RINGING (çalıyor), EARLY (karşı taraf çalıyor), ACTIVE (görüşmede), HELD (beklemede).
Dahililer
{ "data": [
{ "extension": "101", "name": "Satış 1", "enabled": true,
"registered": true, "in_call": false, "user_agent": "Yealink T31P" }
] }registered: telefon santrale bağlı mı. Tıkla-ara yalnız bağlı dahililerle çalışır. in_call: dahili şu an görüşmede mi.
Çağrı geçmişi (CDR)
Dışarıyla yapılan çağrılar (gelen ve giden). Dahililer arası çağrılar listelenmez.
| Parametre | Açıklama |
|---|---|
| from, to zorunlu | YYYY-MM-DD; aralık en çok 31 gün. |
| direction | inbound (gelen) ya da outbound (giden). |
| status | answered, no_answer, busy, failed. |
| extension | Dahili numarası. |
| number | Arayan ya da aranan numara (son 10 hane eşleşir). |
| page, per_page | Sayfalama; per_page en çok 200 (varsayılan 50). Toplam sayı meta.total. |
{ "data": [{
"call_id": "9aeba619-e656-4afd-aacc-ba64dcf24aef",
"direction": "outbound", "from": "101", "to": "05321234567", "extension": "101",
"started_at": "2026-11-12T01:17:50+03:00", "answered_at": "2026-11-12T01:17:54+03:00",
"ended_at": "2026-11-12T01:18:45+03:00", "duration": 51, "talk_time": 51,
"status": "answered", "recording_available": true, "reference": "musteri-4521"
}], "meta": { "page": 1, "per_page": 50, "total": 1 } }duration: toplam süre (sn), talk_time: konuşma süresi (sn).
Tek çağrı
Çağrı geçmişindeki tek satırı döndürür. Çağrı bittikten birkaç saniye sonra kullanılabilir.
Ses kaydı
Kaydı WAV dosyası olarak indirir (voice:recordings). Kayıt yoksa 404.
Webhook olayları
Çağrı olayları, panelde tanımladığınız adrese anında POST edilir: Ayarlar › API & Webhook bölümünde adresi ekleyip olayları seçin.
| Olay | Ne zaman |
|---|---|
| call.ringing | Gelen çağrı dahilileri çaldırmaya başladı (ekranda arayanı göstermek için). Tıkla-ara'da dahili çalmaya başladı. |
| call.answered | Gelen çağrıyı bir dahili açtı; giden/tıkla-ara çağrısında karşı taraf açtı. |
| call.ended | Çağrı bitti. |
{
"id": "3b1f6c2e-8d4a-4f0e-9a51-2c7e0b9d6a14",
"event": "call.ringing",
"data": {
"call_id": "fe4a0d0d-3e2d-451c-a6fa-72f13eef4d8a",
"direction": "inbound",
"from": "905321234567",
"to": "902124189159",
"did": "902124189159",
"ringing_extensions": ["101", "102"],
"reference": null,
"at": "2026-11-12T01:22:31+03:00"
},
"fired_at": "2026-11-12T01:22:31+03:00"
}{
"id": "a90e5d77-41c3-4b2b-8f6d-0d5e3c1f7b28",
"event": "call.ended",
"data": {
"call_id": "fe4a0d0d-3e2d-451c-a6fa-72f13eef4d8a",
"direction": "inbound",
"from": "905321234567",
"to": "902124189159",
"extension": "101",
"status": "answered",
"duration": 9,
"talk_time": 5,
"recording_available": true,
"reference": null,
"at": "2026-11-12T01:22:38+03:00"
},
"fired_at": "2026-11-12T01:22:38+03:00"
}call.answered aynı alanları ve açan extension'ı taşır. Tıkla-ara çağrılarında direction outbound, from dahili, to aranan numara ve reference sizin verdiğiniz değerdir.
2xx dönmeli; başarısız istek bir kez tekrar denenir, yönlendirme (3xx) izlenmez. "Çalıyor" ve "açıldı" olayları 30 saniyeden fazla gecikirse gönderilmez. Webhook'lar anlık bilgi içindir: bir olay kaçarsa kesin kayıt için çağrı geçmişini kullanın.İmza doğrulama
Her isteğin X-VOIS-Signature başlığında, gövdenin webhook gizli anahtarınızla HMAC-SHA256 imzası bulunur: sha256=<hex>. Gizli anahtar panelde webhook'u eklediğinizde gösterilir. İmzası tutmayan istekleri reddedin.
id ve gönderim zamanı fired_at bulunur. İmzayı doğruladıktan sonra fired_at 5 dakikadan eskiyse isteği reddedin; işlediğiniz id'leri bir süre saklayıp aynı olayı ikinci kez işlemeyin. Başarısız bir istek tekrar denendiğinde aynı id ile gelir.<?php $body = file_get_contents('php://input'); // ham gövde — json_decode etmeden önce $expected = 'sha256=' . hash_hmac('sha256', $body, 'GIZLI_ANAHTAR'); if (! hash_equals($expected, $_SERVER['HTTP_X_VOIS_SIGNATURE'] ?? '')) { http_response_code(401); exit; } $event = json_decode($body, true); // $event['event'] === 'call.ringing' → arayanı ekranda göster http_response_code(200);
import crypto from "node:crypto"; // express.raw({ type: "application/json" }) ile ham gövdeyi alın app.post("/vois-hook", express.raw({ type: "application/json" }), (req, res) => { const expected = "sha256=" + crypto.createHmac("sha256", "GIZLI_ANAHTAR").update(req.body).digest("hex"); const got = req.get("X-VOIS-Signature") || ""; if (got.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) return res.sendStatus(401); const event = JSON.parse(req.body); res.sendStatus(200); });
import hmac, hashlib def dogrula(body: bytes, signature: str) -> bool: expected = "sha256=" + hmac.new(b"GIZLI_ANAHTAR", body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, signature or "")
using System.Security.Cryptography; using System.Text; static bool Dogrula(byte[] body, string signature) { using var h = new HMACSHA256(Encoding.UTF8.GetBytes("GIZLI_ANAHTAR")); var expected = "sha256=" + Convert.ToHexString(h.ComputeHash(body)).ToLowerInvariant(); return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signature ?? "")); }
Aranabilen numaralar
Kötüye kullanıma karşı tıkla-ara yalnız aşağıdaki numaraları arar.
| Numara türü | Örnek | Durum |
|---|---|---|
| Cep | 0532 123 45 67 | Açık |
| Sabit (yurt içi) | 0212 418 91 59 | Açık |
| 0850 / 444 | 0850 123 45 67, 444 1 234 | Açık |
| Kendi dahilileriniz | 102 | Açık |
| Yurt dışı | +49 30 1234567 | Kapalı — VOIS açabilir |
| Ücretli (0900, 0898) | 0900 123 45 67 | Kapalı — VOIS açabilir |
| Rehber (118xx), diğer kısa numaralar | 11880 | Kapalı |
| Acil numaralar | 112, 155, 110, 177 … | Her zaman kapalı |
| Özellik kodları | *21*…# | Kapalı |
Panelinizdeki "Aranmasın" (DNC) listesindeki numaralar da aranmaz.
Hata kodları
| Kod | HTTP | Anlamı |
|---|---|---|
| unauthorized | 401 | Anahtar eksik, hatalı ya da iptal edilmiş. |
| voice_not_enabled | 403 | Ses API'si bu hesapta açık değil. |
| insufficient_scope | 403 | Anahtarda bu işlem için gereken yetki yok. |
| invalid_request | 422 | Parametre eksik ya da hatalı; ayrıntı message'da. |
| extension_not_found | 422 | Dahili yok ya da pasif. |
| number_not_allowed | 422 | Numara aranamaz; sebep message'da (bkz. aranabilen numaralar). |
| number_blocked | 422 | Numara "Aranmasın" listesinde. |
| caller_id_not_allowed | 422 | Arayan numara hesabınızın hatlarından biri değil. |
| extension_unavailable | 409 | Dahilinin telefonu santrale bağlı değil. |
| extension_busy | 409 | Dahili şu an görüşmede. |
| auto_answer_not_allowed | 403 | Otomatik cevap bu hesapta açık değil. |
| rate_limited | 429 | Dakikadaki arama sınırı aşıldı. |
| daily_limit | 429 | Günlük API arama sınırına ulaşıldı. |
| too_many_calls | 429 | Aynı anda çok fazla API çağrısı var. |
| not_found | 404 | Çağrı/kayıt yok ya da hesabınıza ait değil. |
| pbx_unavailable | 503 | Santrale geçici olarak ulaşılamıyor; tekrar deneyin. |
Sorun giderme
- Tıkla-ara
extension_unavailabledönüyor: dahilinin telefonu kapalı ya da santrale bağlı değil.GET /extensionsileregistereddeğerine bakın. - 202 dönüyor ama telefon çalmıyor: softphone kullanıyorsanız IPv6'yı kapatıp IPv4 ile yeniden kaydolun; telefonun kayıt adresi santralin ulaşabileceği bir adres olmalı.
- Webhook gelmiyor: panelde webhook'un etkin olduğunu, olayın seçili olduğunu ve adresinizin dışarıdan HTTPS ile erişilebilir olduğunu kontrol edin; paneldeki "Test" düğmesiyle deneyin.
- Çağrı geçmişinde yeni çağrı yok: satır çağrı bittikten birkaç saniye sonra oluşur.