Sohbet widget'ı
Genel sohbet widget'ı, kimliği doğrulanmamış web sitesi ziyaretçilerinin bir çalışma alanıyla konuşma başlatmasını sağlar. Bu sayfa yönetici tarafından yönetilen yapılandırmayı ve kurulum parçacığını kapsar. Ziyaretçiye dönük oturum ve mesajlaşma uçları ayrı belgelenir.
Widget anahtarı
Her tenant'ın bir widget_key değeri vardır — müşterinin web sitesindeki
widget parçacığına gömülen, yüksek entropili, opak bir genel tanımlayıcı
(Intercom'un app_id değeriyle benzer). Sunucu tarafından kayıt sırasında
üretilir ve gizli bir değer değildir:
- Yalnızca genel widget yüzeyi için tenant'ı çözer; tek başına hiçbir ayrıcalık vermez.
- İstemci girdisinden asla yazılamaz (yalnızca sunucu üretir).
- Ziyaretçi yetkilendirmesi ayrı bir imzalı oturum token'ı ile sağlanır — widget anahtarı tek başına hiçbir konuşmayı okuyamaz veya yazamaz.
Ham tenant id'si istemciden asla kabul edilmez; widget yüzeyi tenant'ı
yalnızca widget_key üzerinden çözer (X-Widget-Key başlığı veya widget_key
parametresi olarak gönderilir). Eksik veya bilinmeyen anahtar reddedilir (404,
fail-closed).
Yönetici yapılandırması
Yalnızca yönetici (manage-widget-settings yetkisi). Tenant izolasyonu, altta
yatan tenant_settings satırındaki Satır Düzeyi Güvenlik (RLS) ile sağlanır.
| Uç | Açıklama |
|---|---|
GET /api/widget/settings | Etkin widget yapılandırmasını okur. |
PUT /api/widget/settings | Kısmi güncelleme (tüm alanlar opsiyonel). |
GET /api/widget/install | Genel widget_key ve gömme parçacığını okur. |
Ayarlar
| Alan | Tip | Notlar |
|---|---|---|
enabled | boolean | Ana anahtar. Varsayılan kapalı. |
name | string | null | Başlık görünen adı (≤ 120 karakter). |
welcome_message | string | null | Açılışta gösterilen karşılama (≤ 2000 karakter). |
primary_color | string | Hex renk, örn. #2563eb. |
locale | string | Desteklenen dillerden biri. |
Her değer geneldir (kimliği doğrulanmamış ziyaretçilere sunulur); doğrulama kuralları gizlilik için değil, arayüz güvenliği içindir (uzunluk sınırları, katı hex renk, desteklenen dil listesi).
Kurulum parçacığı
GET /api/widget/install şunu döner:
{
"data": {
"widget_key": "…",
"script_url": "https://app.example.com/widget.js",
"snippet": "<script>…</script>"
}
}
snippet değerini hedef web sitesine yapıştırın. Widget yükleyici betiğini
yükler ve widget'ın çalışma alanını çözebilmesi için genel widget_key değerini
geçirir.
Yükleyici ayarları: öznitelik veya sorgu dizesi
Yükleyici anahtarını şu sırayla okur: window.SmaibotWidgetKey, script
etiketindeki data-widget-key özniteliği, ardından script URL'sindeki ?key=.
İsteğe bağlı locale, name, welcome-message ve manual geçersiz kılmaları
da önce data-<ad>, sonra ?<ad>= üzerinden okunur; dolayısıyla şu iki etiket
eşdeğerdir:
<script async src="https://app.example.com/widget.js"
data-widget-key="wk_…" data-locale="tr" data-manual="true"></script>
<script async src="https://app.example.com/widget.js?key=wk_…&locale=tr&manual=true"></script>
Sorgu dizesi biçimi etiket yöneticileri için vardır: Google Tag Manager'ın Özel
HTML etiketi script öğelerini yeniden oluşturur ve tüm data-* özniteliklerini
siler, ama src'yi korur. Sorgu değerleri URL-kodludur (yerelleştirilmiş bir
name / welcome-message, URL-kodlu {default, translations} JSON nesnesidir).
data-sso-token, data-ephemeral ve data-api-base-url yalnızca öznitelik
olarak okunur — URL'deki bir token CDN ve proxy günlüklerine düşer. Hiçbir yerde
anahtar yoksa yükleyici hiçbir şey yapmaz.
Widget varlıklarını sunma (CORS)
Yükleyici (widget.js) klasik bir betiktir ve cross-origin olarak özel bir
yapılandırma gerektirmeden yüklenir. Ardından uygulama bundle'ını
(widget-app.js) bir ES modülü olarak import eder ve tarayıcılar modül
betiklerini CORS modunda getirir. Bu yüzden widget bir müşteri sitesine (varlığın
sunulduğu origin'den farklı) gömüldüğünde, bundle'ı sunan host — CDN'iniz veya
uygulama origin'iniz — widget.js, widget-app.js ve assets/ altındaki hash'li
parçalarda Access-Control-Allow-Origin: * göndermelidir. Aksi halde modül
import'u engellenir ve widget hiç eklenmez.
Bu, aşağıdaki widget API CORS'undan bağımsızdır (onu uygulamanın kendisi
yönetir). Yükleyici varsayılan olarak API origin'ini kendi betik URL'sinden türetir;
API'yi varlıklardan farklı bir host'a yöneltmek için parçacıkta data-api-base-url
geçirin.
Ziyaretçi oturumu (genel yüzey)
Anonim ziyaretçiler personel kimlik bilgileri yerine imzalı bir oturum token'ı ile kimlik doğrular. Oturum şununla kurulur:
| Uç | Yetki | Açıklama |
|---|---|---|
POST /api/widget/session | widget_key (+ opsiyonel token) | Yeni ziyaretçi oturumu başlat veya devam ettir. |
Çerez veya sunucu tarafı oturum satırı yoktur — token kendinden taşıyıcı, durumsuz bir kimlik bilgisidir.
Başlatma ve devam ettirme
- Yeni ziyaretçi —
widget_key(başlıkX-Widget-Keyveya gövde) gönderir, token göndermez. Sunucu yeni bir ziyaretçi (visitor) kişisi ve açık sohbet (open chat) konuşması oluşturur, ardından bir oturum token'ı döner. Yanıt durumu201. - Geri dönen ziyaretçi — ek olarak önceden verilen token'ı
Authorization: Bearer <token>(veyaX-Widget-Session) ile sunar. Sunucu token'ı doğrular ve aynı konuşmaya devam eder. Yanıt durumu200. - Geçersiz, kurcalanmış, süresi dolmuş veya başka tenant'a ait token token yokmuş gibi ele alınır — ziyaretçi şeffaf biçimde yeni bir oturum başlatır (asla hata değil, asla başkasının konuşmasına devam değil).
İstek gövdesi opsiyoneldir ve yalnızca düşük güvenli görüntüleme alanları taşır:
İstemciden asla tenant / kişi / konuşma id'si kabul edilmez. Widget tenant için
etkin olmalıdır, aksi halde uç 403 döner.
Oturum token'ı
Token şifrelenmiş + kimlik doğrulamalıdır (Laravel Crypt) ve ziyaretçiyi
tam olarak bir {tenant, kişi, konuşma} kapsamına bağlar:
- Kurcalama veya sahtecilik bütünlük kontrolünü geçemez ve reddedilir.
- Bir verilme zamanı (issued-at) taşır ve
config('widget.session_ttl_days')(varsayılan 30 gün) sonra geçersiz olur. - Tek konuşmasının ötesinde hiçbir erişim vermez; tenant'lar veya konuşmalar arası kullanım yapısal olarak imkânsızdır.
- Token'ı istemcide gizli bir kimlik bilgisi olarak ele alın.
Yanıt
{
"data": {
"token": "…",
"is_new": true,
"config": { "name": "…", "welcome_message": "…", "primary_color": "#2563eb", "locale": "en", "agents_online": true },
"conversation": { "id": "…", "status": "open", "created_at": "…" },
"messages": [
{ "id": "…", "author": "visitor", "sender_type": "contact", "body": "…", "created_at": "…" }
]
}
}
Ziyaretçi verisi bilinçli olarak dardır: personel kimliği, AI meta verisi
(meta), kişi PII / özel öznitelikleri veya başka tenant'ın verisini asla
içermez. Her mesaj author: "visitor" | "staff" olarak normalize edilir; bir
personel/bot sender_id değeri asla sızdırılmaz (yalnız ziyaretçinin kendi
mesajları için kendi kişi id'si yansıtılır).
config.agents_online, çıplak bir boolean'dır — tenant'ın en az bir personeli şu
anda Çevrimiçi presence'e ayarlıyken true'dur (AgentAvailability::anyOnline()).
Widget'ın hangi ajanın (veya kaç ajanın) müsait olduğunu asla açığa çıkarmadan bir
"çevrimiçi" ipucu göstermesini sağlar.
config.ai_available yalnızca AI asistanı bir widget sohbetini gerçekten
yanıtlayacaksa true'dur: widget'ın AI anahtarı açık ve
AiManager::isEnabled() (planda AI özelliği, tenant'ın AI anahtarı, kullanılabilir
bir anahtar). false iken widget "AI asistan" etiketi ve "temsilciye bağlan"
düğmesi göstermez; başlıkta agents_online'dan ekibin durumunu gösterir.
config.commerce.enabled yalnızca planda katalog ve satış varsa, çalışma alanı
widget mağazasını açtıysa (widget ayarı shop_enabled, varsayılanı false;
Ayarlar → Widget → Genel altındaki "Mağazayı widget'ta göster") ve en az bir
ürün widget üzerinden sipariş edilebiliyorsa (product türünde, aktif, fiyatlı,
para birimi var, widget'tan gizlenmemiş; hizmetler ve paketler burada satılmaz)
true'dur. Aynı tanım GET /api/widget/catalogue'un da arkasındadır; planda
katalog ya da satış yoksa veya shop_enabled kapalıysa bu uç (ve POST /api/widget/orders) 404 döner. GET /api/widget/orders (ziyaretçinin kendi
siparişleri) bu anahtara bağlı değildir: mağazayı kapatmak girişi gizler, hiçbir
şeyi silmez.
AI araçları
Bir çalışma alanı widget'ın içinde AI aracı profilleri sunabilir (doc 97 §13).
Oturum yapılandırması bunları ai_tools içinde listeler — yalnızca çalışma
alanının widget için açtığı profiller, sayfanın alan adı (Origin) için,
ziyaretçinin dilinde: etiket, açıklama, simge, hazır formats (anahtar +
etiket, asla istem değil), per_day, max_upload_mb ve version'ıyla
(metnin sha256'sı) birlikte consent metni.
| Uç nokta | Kimlik | Açıklama |
|---|---|---|
POST /api/widget/tools/runs | oturum belirteci | Multipart: module, profile, photo, consent_version, isteğe bağlı format, locale. Çalıştırmayla 201 (state pending, ya da profilin message'ıyla refused). |
GET /api/widget/tools/runs/{run} | oturum belirteci | Ziyaretçinin KENDİ çalıştırması; delivered olunca result.url kısa ömürlü imzalı bir bağlantıdır. |
Çalıştırma belirteçteki kişiye aittir. Güncel onay sürümü olmadan başlatma
409 consent_required döner ve hiçbir şey saklanmaz; sunulmayan (ya da bu alan
adında sunulmayan) profil 404 surface_off. Başlatma oturum ve IP başına
saatlik, widget_key başına dakikalık sınırlıdır
(tool_modules.direct.widget_rate_limits); profilin kişi başına günlük limiti
de geçerlidir. Profilde widget.post_to_conversation açık değilse konuşmaya
hiçbir şey yazılmaz; açıksa (kayıt posts_to_conversation: true der, onay
metni de bunu söyler) biten sonuç ziyaretçinin konuşmasına görselli bir bot
mesajı olarak da gönderilir ve widget onu diğer ekler gibi gösterir.
Mesajlaşma
Oturum kurulduktan sonra ziyaretçi, oturum token'ını Authorization: Bearer <token> başlığında (veya X-Widget-Session) ve widget_key ile birlikte
sunarak mesaj gönderir ve okur.
| Uç | Yetki | Açıklama |
|---|---|---|
POST /api/widget/messages | oturum token'ı | Ziyaretçinin konuşmasına mesaj gönderir. |
GET /api/widget/messages | oturum token'ı | Ziyaretçinin konuşma geçmişini sayfalar. |
Konuşma her zaman oturum token'ından alınır, istekten asla — ziyaretçi
yalnızca kendi konuşmasına erişebilir. G önderilen her mesaj sunucu
tarafında sender_type: contact ve chat kanalı olarak yazılır;
sender_type, sender_id, channel ve konuşma id'si istemciden gelirse yok
sayılır, böylece ziyaretçi bir personel/bot mesajını taklit edemez veya başka
bir konuşmayı hedefleyemez.
Ziyaretçi mesajı bir contact mesajı olduğu için, herhangi bir kanal gibi aynı gelen-mesaj hattından geçer — çalışma alanında AI etkinse AI otomatik yanıtı dahil.
Blok kısıtlamaları
Ziyaretçi mesajı boş olmayan body metni ve/veya bir blocks dizisi taşımalıdır.
Ziyaretçiler yalnızca kısıtlı blok türlerini gönderebilir — text, image
ve file. Personele özel etkileşimli bloklar (buttons, card, slider,
translation) 422 ile reddedilir. Gövde uzunluğu ve blok sayısı sınırlıdır
(config('widget.message_max_chars') / message_max_blocks).
{ "body": "Merhaba, yardıma ihtiyacım var" }
Gerçek zamanlı
Widget, atanan ajanın izlediği aynı özel konuşma kanalı üzerinden canlı mesaj
alır — private-tenant.{tenantId}.conversation.{conversationId} — böylece
ziyaretçi ve ajan tek bir ortak akış görür.
Ziyaretçinin personel kimlik bilgileri olmadığından, Pusher/Echo istemcisi
authEndpoint değerini özel, genel bir broadcasting-auth ucuna yönlendirir:
| Uç | Yetki | Açıklama |
|---|---|---|
POST /api/widget/broadcasting/auth | oturum token'ı | Ziyaretçinin kendi konuşma kanalını imzalar. |
Bu uç yalnızca ziyaretçinin kendi konuşma kanalını — token'da adı geçeni —
yetkilendirir. Başka herhangi bir kanal (başka konuşma, ajan presence kanalı
presence-tenant.{t}.agents, bir kullanıcının kişisel kanalı, farklı bir tenant)
reddedilir (403). Konuşma id'si yalnızca token'dan geldiği için
çapraz-konuşma / çapraz-tenant abonelik yapısal olarak imkansızdır.
CORS
Widget herhangi bir müşteri web sitesine gömülür, bu nedenle widget uçları
(api/widget/*) özel, kimlik-bilgisiz bir CORS politikası kullanır:
Access-Control-Allow-Origin: * ve Access-Control-Allow-Credentials yok.
Bu, tam olarak widget çerezsiz olduğu için güvenlidir — oturum token'ı bir istek
başlığında taşınır, dolayısıyla CSRF yüzeyi yoktur ve joker origin kimlik
bilgileriyle birleştirilemez.
Bu, sıkı açık-origin izin listesini kimlik bilgileriyle koruyan personel/SPA
CORS'undan (config/cors.php) ayrıdır. İkisi asla örtüşmez: genel CORS
işleyicisi widget yolları için atlanır ve bunlar yerine WidgetCors ara
yazılımıyla sunulur.
Hız sınırlama
Genel widget yüzeyi katmanlı olarak hız-sınırlıdır (dakika başına,
config('widget.rate_limits') içinde yapılandırılır):
POST /session—widget_key+ istemci IP'siyle anahtarlanır (oturum oluşturma spam'ı).- Mesajlaşma + broadcast auth — iki yığılı tavan: ziyaretçi oturumu başına
(tek bir gevezelik eden ziyaretçi taşamasın) ve daha kaba
widget_keybaşına (gürültülü-komşu: bir çalışma alanı yüzeyi diğerleri için tüketemesin).
Bir sınır aşıldığında Retry-After ve X-RateLimit-* başlıklarıyla 429
döner.
Yetkili istek/yanıt şekilleri için OpenAPI spesifikasyonuna bakın
(apps/api/openapi/openapi.yaml, etiket Widget).