npmjs.com ↗
Kurulumnpm install signalbird · composer require pariette/signalbird · pip install signalbird

Yönetim (Management) - kendi projenizi kodla yönetin

Panelde tıklayarak yaptığınız her şeyi kodla yapar: Telsiz kanalı açmak, olay akışını okumak, sohbet gelen kutusunu işlemek, uygulama kaydı ve cihaz listesi yönetmek.

Bu bir admin yüzeyi değildir. Anahtar tek bir takıma bağlıdır ve yalnız o takımın kayıtlarına dokunur; başka takımın kaydı 404 döner. Kullanıcı yönetimi, faturalama, abonelik ve plan işlemleri SDK'da yoktur ve olmayacaktır - onlar panelin ve şirket sahibinin işidir.

Ne zaman işinize yarar:

  • Ortam kurulumu: her yeni ortam için (staging, prod, müşteri kurulumu) Telsiz kanallarını CI'da açmak.
  • Kendi ajan arayüzünüz: sohbeti kendi panelinize gömmek ya da CRM'e köprülemek.
  • Bot: gelen kutusuna otomatik ilk yanıt yazmak, konuşmayı sınıflandırmak.
  • Mobil sürüm çıkarken uygulama kaydını elle açmak zorunda kalmamak.

Anahtar

Kapsam (scope) sistemi 1 Eyl 2026'da kalktı. Yönetim, gönderimle aynı anahtarı kullanır: gizli domain anahtarı (sb_secret_live_…). Panelde Alan adları → [alan adı] → Anahtarlar → Yeni anahtar ile üretilir, yalnız bir kez gösterilir ve projenizin .env dosyasına SIGNALBIRD_DOMAIN_KEY olarak konur. Sunucu anahtarı için alan adının DNS doğrulaması gerekmez.

Gizli anahtar yalnız sunucuda çalışır; Origin başlığı taşıyan (tarayıcıdan gelen) istekte 401 SECRET_KEY_IN_BROWSER döner. Açık anahtar (sb_public_live_…) yönetim uçlarını hiç açmaz.

Kurulum

bash
npm install signalbird          # Node.js / TypeScript
composer require pariette/signalbird      # PHP / Laravel
pip install signalbird               # Python
go get github.com/Pariette-Inc/signalbird.sdk/v2
dotnet add package Signalbird.Sdk

Metot adları beş dilde de birebir aynıdır; yalnız yazım geleneği değişir (createModuleKey / create_module_key / CreateModuleKey / CreateModuleKeyAsync).

Hızlı başlangıç

typescript
import { management } from 'signalbird'

// SIGNALBIRD_DOMAIN_KEY ortam değişkeninden okunur
const created = await management().createModuleKey('logger', { name: 'ödeme-servisi' })

// `secret` YALNIZ burada döner: sunucuda yalnız SHA-256 özeti saklanır.
// Kaybederseniz panelden yenisini üretirsiniz; geri okunamaz.
await vault.write('SIGNALBIRD_DOMAIN_KEY', created.data!.secret)

Kendi istemcinizi kurmak isterseniz:

typescript
import { SignalbirdManagement } from 'signalbird'

const admin = new SignalbirdManagement({
  domainKey: process.env.SIGNALBIRD_DOMAIN_KEY!,
  timeout: 15000,
  throwOnError: false,   // varsayılan; geliştirmede açabilirsiniz
})

Açık anahtar verirseniz (sb_public_live_…) ilk istekte değil, istemciyi kurarken WRONG_KEY_TYPE hatası alırsınız.

Sonuç biçimi

Her metot aynı zarfı döner:

typescript
{ ok: true,  status: 201, data: { … } }
{ ok: false, status: 403, code: 'API_KEY_SCOPE', message: '…', data: { … } }

throwOnError: true iken hata yerine SignalbirdError fırlatılır (code, status, body taşır).


Telsiz yönetimi

Kanallar

Telsiz'de proje yoktur; kanal, modül anahtarıdır (logger modülü). Kanal bildirim kuralının durduğu yerdir: kim alacak, push mu e-posta mı, sessiz saatlerde ne olacak, aynı mesaj kaç kez uyaracak. Kanalın gizli bir sırrı YOKTUR: adı kodun içinde durur. Gizli olan tek şey domain anahtarıdır ve panelde üretilir.

typescript
await admin.listModuleKeys('logger')

const { data } = await admin.createModuleKey('logger', {
  title: 'Ödeme',
  key: 'odeme',              // kodunuzda radio('odeme') diye çağıracağınız ad
  level: 'critical',         // kanalın varsayılan seviyesi
  notify: ['push', 'email'],
  recipient_user_ids: [12, 34],   // boş = bütün takım
  quiet_from: 0,
  quiet_to: 7,               // kritik seviye sessiz saatleri yine de deler
  dedupe_seconds: 300,       // aynı mesajı 5 dk içinde tekrar duyurma
})

await admin.getModuleKey('logger', data!.module_key.id)
await admin.updateModuleKey('logger', data!.module_key.id, { level: 'error' })
await admin.deleteModuleKey('logger', data!.module_key.id)

key değiştirilebilir; eski ad 30 gün daha kabul edilir, böylece üretimdeki kod bir sonraki yayına kadar kayıt kaybetmez.

Tanımsız bir ada log gelirse kanal kendiliğinden ve sessiz açılır (bildirim kuralını ekip koyar). AWS SNS / CloudWatch bağlantısı için kanalın gelen bağlantı adresi panelden üretilir (Telsiz → Kanallar → AWS / Webhook).

Olay akışı

typescript
await admin.radioSummary()

await admin.radioEvents({
  key: 'odeme',
  level: ['critical', 'error'],
  q: 'ödeme',
  from: '2026-08-01',
  page: 1,
  per_page: 50,
})

Sohbet - ajan tarafı

Panelin Gelen Kutusu ekranıyla aynı uçlar. "Ajan" olarak anahtarı üreten kullanıcı kaydedilir; sahipsiz (miras) anahtar yazma yapamaz.

typescript
const inbox = await admin.listConversations({ status: 'open', per_page: 20 })

const detail = await admin.getConversation(conversationId)
const messages = await admin.listConversationMessages(conversationId, {
  after: lastMessageId,       // cm_… imleci; tam listeyi tekrar çekmez
  include_internal: true,     // iç notlar - ziyaretçi bunları asla görmez
})

Yanıtlama

typescript
await admin.reply(conversationId, { body: 'Merhaba! Birazdan dönüyoruz.' })

// İç not: gelen kutusunda görünür, ziyaretçiye ASLA gitmez
await admin.reply(conversationId, { body: 'Ödeme ekibine soruldu', is_internal: true })

await admin.editChatMessage(conversationId, messageId, 'Düzeltilmiş metin')
await admin.deleteChatMessage(conversationId, messageId)
await admin.reactToChatMessage(conversationId, messageId, '👍')  // ikinci kez → kaldırır

Akış yönetimi

typescript
await admin.assignConversation(conversationId)        // anahtarın sahibine ata
await admin.assignConversation(conversationId, 42)    // belirli üyeye ata
await admin.setConversationStatus(conversationId, 'resolved')
await admin.updateConversation(conversationId, { priority: 'high', tags: ['ödeme'] })
await admin.readConversation(conversationId, lastMessageId)
await admin.setTyping(conversationId, true)

Ziyaretçi ve hazır yanıtlar

typescript
await admin.getVisitor(visitorId)
await admin.updateVisitor(visitorId, { name: 'Ayşe Yılmaz', attributes: { plan: 'pro' } })
await admin.banVisitor(visitorId)

await admin.listCannedReplies()
await admin.createCannedReply({ shortcut: '/kargo', title: 'Kargo', body: 'Kargonuz yolda.' })
await admin.updateCannedReply(id, { body: 'Kargonuz bugün çıktı.' })
await admin.deleteCannedReply(id)

Bot deseni

typescript
// Her dakika: yeni açılan konuşmalara ilk yanıtı yaz
const inbox = await management().listConversations({ status: 'open' })

for (const conversation of inbox.data?.data ?? []) {
  if (conversation.unread_count === 0) continue

  await management().reply(conversation.id, {
    body: 'Merhaba! Ekibimiz birkaç dakika içinde yanıtlayacak.',
  })

  await management().setConversationStatus(conversation.id, 'pending')
}

Yoklama aralığını makul tutun: okuma sınırı dakikada 240, yazma 60 istektir.


Sohbet - tetikleyiciler

"Şu olduğunda şunu yap." Kural kayıtta durur, kodda değil: müşteri davranışı değiştirmek için sürüm çıkarmak zorunda kalmasın.

ts
await management().createChatTrigger({
  name: 'Fiyat sorusu → satış',
  event: 'visitor.message',          // conversation.created | visitor.message | no_reply
  conditions: {
    match: 'all',
    rules: [{ field: 'body', op: 'contains', value: 'fiyat' }],
  },
  actions: [
    { type: 'reply', body: 'Merhaba! Fiyat listemizi hemen paylaşıyoruz.' },
    { type: 'tag', value: 'satış' },
    { type: 'priority', value: 'high' },
  ],
})
Alan Değerler
event conversation.created · visitor.message · no_reply (delay_seconds sonra, hâlâ yanıt yoksa)
koşul alanı body · page_url · source · within_hours · visitor_is_new · visitor_email · tag
işleç contains · not_contains · eq · neq · exists · not_exists · is_true · is_false
eylem reply · internal_note · tag · priority · assign

Koşul listesi boş bırakılabilir: tetikleyici o olayın her seferinde çalışır (karşılama mesajı böyle kurulur). Aynı tetikleyici aynı konuşmada bir kez konuşur; yoksa "fiyat" geçen her mesaja aynı yanıt giderdi.

Otomatik yanıt system göndericisiyle yazılır, ajan olarak değil. Ajan gibi görünseydi ziyaretçi karşısında insan olduğunu sanır, first_response_at damgası da yalan söyler ve SLA raporunuz bozulurdu.

Atama eylemi yalnız atanmamış konuşmada çalışır: bir insan üstlendiyse otomasyon onu masadan almaz.


Sohbet - rapor

ts
const { data } = await management().chatReport('30d')   // 7d | 30d | 90d
json
{
  "volume": { "total": 412, "open": 18, "resolved": 380, "closed": 14, "unanswered": 6 },
  "first_response": { "median_s": 96, "p90_s": 640, "answered": 406 },
  "resolution":     { "median_s": 1820, "p90_s": 9400, "resolved": 380 },
  "satisfaction":   { "average": 4.6, "count": 210, "breakdown": { "5": 160, "4": 35 } },
  "agents": [{ "user_id": 7, "name": "Zeynep", "assigned": 190, "replies": 812,
               "median_first_response_s": 74, "rating_average": 4.8 }]
}

İki şeyi bilerek böyle yapıyoruz:

  • Ortalama değil ortanca + p90. Tek bir gece yarısı yanıtı ortalamayı saatlerce yukarı çeker ve kimsenin tanımadığı bir sayı üretir. Ortanca tipik günü, p90 kötü günü anlatır.
  • Veri yoksa null, 0 değil. "0 saniyede yanıtlıyoruz" rapor ekranındaki en tehlikeli yalandır. Ekranınızda "-" gösterin.

volume.unanswered raporun en önemli sayısıdır: hiç ajan yanıtı görmemiş konuşmalar.


Uygulamalar

Uygulama, müşterinizin sitesine ya da mobil uygulamasına gömülen açık anahtarın (sb_public_live_…) sahibidir.

typescript
const app = await admin.createModuleKey('chat', {
  name: 'Mobil - iOS',
  platform: 'ios',
  chat_enabled: true,
  push_enabled: true,
})

// public_key gizli DEĞİLDİR; zaten istemciye gömülür
console.log(app.data!.public_key)     // sb_public_live_…

await admin.updateModuleKey('chat', id, { allowed_origins: ['https://ornek.com'] })
await admin.updateModuleKey('chat', id)          // eski anahtar ANINDA geçersiz
await admin.listModuleKeyDevices('push', id, { page: 1 })   // token maskeli döner
await admin.deleteModuleKey('chat', id)

Anahtar yenilemek kanal adını DEĞİŞTİRMEZ: domain anahtarını panelden yenilersiniz, sitenizdeki data-channel aynı kalır. v1'de anahtar yenilemek gömme satırını kırıyordu.


Signalbird ekranını KENDİ panelinizde göstermek

Gelen kutusunu, monitörleri ya da kampanya ekranını kendi yönetim panelinize gömebilirsiniz. Ekran Signalbird'ün kendisidir - kopyası değil; biz güncelledikçe sizinki de güncellenir.

ts
const { data } = await admin.embedToken({ module: 'chat' })

// data.url → 120 saniyelik, TEK KULLANIMLIK adres
// <iframe src={data.url} />
php
$embed = Signalbird::management()->embedToken(['module' => 'chat']);
$url = $embed['data']['url'];

Modüller: chat · topics · messages · contacts · campaigns · monitoring · radio · members.

Üç şey bilinmeli:

  1. Adresi sunucunuzda üretin. Takım anahtarı tarayıcıya inmemeli; tarayıcıya yalnız kısa ömürlü adres iner.
  2. Jeton 120 saniye yaşar ve bir kez kullanılır. URL'de gittiği için Referer başlığına ve loglara düşer; kısa ömür bunun içindir. İframe yüklendikten sonra 60 dakikalık bir oturum kurulur.
  3. Anahtarınızın embed:issue kapsamı olmalı. Bu kapsam bilerek ayrıdır: gömme jetonu bir panel oturumuna çevrilir, yani dar kapsamlı bir anahtarın üretebilmesi kapsam kısıtını anlamsız kılardı. user_id vermezseniz anahtarın sahibi kullanılır; verdiğinizde o kişi takımın üyesi olmalıdır.

Hata kodları

Kod Anlamı
WRONG_KEY_TYPE sb_ dışı anahtar verildi - kurulum anında
API_KEY_INVALID Anahtar yok, süresi dolmuş ya da iptal edilmiş
API_KEY_SCOPE Anahtarda gereken kapsam yok (required_scope alanına bakın)
API_KEY_IP_BLOCKED Anahtar IP kısıtına takıldı
MODULE_DISABLED Paketinizde bu modül yok (logger, chat)
VALIDATION_ERROR 422 - gövde doğrulamadan geçmedi
NETWORK_ERROR / TIMEOUT Ağ (status 0)
HTTP_<durum> Sunucu kod vermediyse

404 iki anlama gelir ve ayırt edilmez: kayıt yok ya da başka bir takımın. Bu kasıtlıdır - aksi hâlde kimlik numarası deneyerek başka takımların kayıt sayısı öğrenilebilirdi.

Diğer diller

php
// PHP / Laravel
Signalbird::management()->createModuleKey('logger', ['name' => 'ödeme-servisi']);
Signalbird::management()->reply($conversationId, ['body' => 'Merhaba!']);
python
# Python
admin = SignalbirdManagement(domain_key=os.environ["SIGNALBIRD_DOMAIN_KEY"])
admin.create_module_key({"name": "ödeme-servisi"})
admin.reply(conversation_id, {"body": "Merhaba!"})
go
// Go
admin, _ := signalbird.NewManagement(signalbird.KeyConfig{DomainKey: key})
admin.CreateModuleKey(ctx, "logger", map[string]any{"title": "Ödeme hataları"})
csharp
// .NET
await management.CreateModuleKeyAsync("logger", new Dictionary<string, object?> { ["title"] = "Ödeme hataları" });

Tam metot listesi (45 metot) ve davranış kuralları: signalbird.sdk/docs/CONTRACT.md § 10.