Webhook
Bir kullanıcı sohbetten destek talebi açtığında Kavchat bu talebi hemen sisteminize gönderebilir: destek sisteminize, CRM'inize veya bir Slack botuna. Her gönderim gizli anahtarınızla imzalanır.
Webhook, Premium planına dahildir.
Kurulum
- Sunucunuzda JSON gövdeli
POSTisteklerini kabul eden bir HTTPS uç noktası oluşturun. - Yönetim panelinde Ayarlar bölümünü açın ve adresi Webhook alanına girin.
- Her isteğin imzasını aşağıda açıklandığı gibi doğrulayın ve herhangi bir
2xxdurum koduyla yanıt verin.
Adres https:// ile başlamalı ve herkese açık bir adrese yönlendirmelidir. Özel ağlardaki adresler engellenir.
Mesaj içeriği
Kavchat iki tür olay gönderir: ticket.created, destek talebi formundan gönderilen talepler için; lead.created ise bir ziyaretçinin senaryodaki potansiyel müşteri adımında bıraktığı bilgiler için kullanılır. data alanı, sohbet dökümü dahil olmak üzere destek talebini veya potansiyel müşteri kaydını Sunucu API'sinin döndürdüğü yapıyla içerir.
POST /kavchat/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Kavchat-Webhook/1
Kavchat-Signature: t=1792915200,v1=5f2b7c...e91a{
"type": "ticket.created",
"data": {
"id": "6718f0c2a4b5c6d7e8f90123",
"number": 128,
"status": "open",
"name": "Dana",
"email": "dana@example.com",
"phone": "+972501234567",
"userId": "user_123",
"topicId": "billing",
"topicName": "Billing",
"scriptId": "returns",
"sessionId": "k3J9xQ2mP7aB",
"message": "My refund has not arrived yet.",
"transcript": [
{ "from": "bot", "text": "Hi! How can I help?", "stepId": "start", "at": "2026-10-25T09:14:02.000Z" },
{ "from": "user", "text": "Returns", "at": "2026-10-25T09:14:05.000Z" }
],
"createdAt": "2026-10-25T09:15:40.000Z",
"updatedAt": "2026-10-25T09:15:40.000Z"
}
}userId, kullanıcı tanımlandığında sub alanının kimlik belirtecindeki değeridir; aksi halde null olur. name, email, phone ve konu alanları da null olabilir.
Potansiyel müşteriler ve kişisel bilgiler
Bir senaryo ziyaretçiye sorular sorabilir (ad, telefon, e-posta, sayı veya serbest metin), her yanıtı doğrulayabilir ve bir değişkende saklayabilir. Potansiyel müşteri adımı bu bilgileri yönetim panelindeki potansiyel müşteri kutusuna gönderir. lead.created olayında data.variables alanı, toplanan tüm değerleri değişken adına göre içerir. Yaygın kullanılan değerler ayrıca data.name, data.email ve data.phone alanlarında da yer alır. Telefon numaraları uluslararası biçimde (E.164) gönderilir.
Bu bilgiler kişisel veridir. Kavchat bunları yalnızca ait oldukları potansiyel müşteri kaydında, destek talebinde veya temsilciyle yapılan sohbette saklar; raporlarda veya günlüklerde asla saklamaz. Potansiyel müşteri kayıtları, seçtiğiniz saklama süresinin sonunda silinir (ücretsiz planda 30 gün; Premium'da silmeden saklamak da mümkündür). Tek bir potansiyel müşteri kaydını istediğiniz zaman silebilirsiniz. Kişisel bilgi topluyorsanız bunu gizlilik politikanızda belirtin. Potansiyel müşteri adımının onay metninde, ziyaretçilere bilgilerini nasıl kullanacağınızı açıklayın.
Kavchat-Signature başlığı
Başlık şu biçimdedir: t=<unix seconds>,v1=<hex>. Burada:
t, Unix saniyesi cinsinden gönderim zamanıdır.v1, onaltılık biçimde kodlanmışHMAC-SHA256(secretKey, "<t>.<raw body>")değeridir. Anahtar olarak gizli anahtarınızın tamamı kullanılır.
Doğrulamak için aynı HMAC değerini, tam olarak geldiği haliyle ham gövde üzerinden hesaplayın, sabit zamanlı karşılaştırma yapın ve 5 dakikadan eski istekleri reddedin. Ayrıştırılıp tekrar JSON'a dönüştürülmüş bir gövde imzayla eşleşmez.
SDK ile doğrulama
verifyWebhook tüm bu işlemleri yapar ve ayrıştırılmış olayı döndürür. Başarısız olduğunda şu kodlardan biriyle hata fırlatır: malformed_header, timestamp_out_of_range, invalid_signature veya invalid_payload. Bu hatalara HTTP 400 ile yanıt verin. SDK'ların kurulumu Kullanıcı tanımlama bölümünde açıklanmıştır.
import express from 'express';
import { Kavchat, WebhookVerificationError } from '@kavchat/node';
const kavchat = new Kavchat(process.env.KAVCHAT_SECRET_KEY!);
// Use the raw body. A body that was parsed and serialized again will not match the signature.
app.post('/kavchat/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = kavchat.verifyWebhook(req.body, req.get('Kavchat-Signature'));
if (event.type === 'ticket.created') console.log('New ticket', event.data.number);
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.status(400).send(err.code);
throw err;
}
});from flask import Flask, request
from kavchat import WebhookVerificationError
app = Flask(__name__)
@app.post("/kavchat/webhook")
def webhook():
try:
# raw bytes: request.get_data() in Flask, request.body in Django
event = kavchat.verify_webhook(request.get_data(), request.headers.get("Kavchat-Signature"))
except WebhookVerificationError as err:
return err.code, 400
if event["type"] == "ticket.created":
print("New ticket", event["data"]["number"])
return "", 200use Kavchat\WebhookVerificationException;
try {
$event = $kavchat->verifyWebhook(
file_get_contents('php://input'),
$_SERVER['HTTP_KAVCHAT_SIGNATURE'] ?? null,
);
} catch (WebhookVerificationException $e) {
http_response_code(400);
exit($e->errorCode);
}
if ($event->type === 'ticket.created') {
error_log('New ticket ' . $event->data->number);
}@PostMapping("/kavchat/webhook")
public ResponseEntity<String> webhook(@RequestBody byte[] body,
@RequestHeader(value = "Kavchat-Signature", required = false) String signature) {
try {
WebhookEvent event = kavchat.verifyWebhook(body, signature);
if ("ticket.created".equals(event.getType())) log.info("New ticket {}", event.getData().getNumber());
return ResponseEntity.ok("");
} catch (WebhookVerificationException e) {
return ResponseEntity.badRequest().body(e.getCode());
}
}app.MapPost("/kavchat/webhook", async (HttpRequest request, KavchatClient kavchat) =>
{
using var ms = new MemoryStream();
await request.Body.CopyToAsync(ms);
try
{
var evt = kavchat.VerifyWebhook(ms.ToArray(), request.Headers[KavchatWebhook.SignatureHeader]);
if (evt.Type == "ticket.created") Console.WriteLine($"New ticket {evt.Data.Number}");
return Results.Ok();
}
catch (WebhookVerificationException e)
{
return Results.BadRequest(e.Code);
}
});http.HandleFunc("/kavchat/webhook", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
event, err := client.VerifyWebhook(body, r.Header.Get(kavchat.SignatureHeader))
var werr *kavchat.WebhookError
if errors.As(err, &werr) {
http.Error(w, werr.Code, http.StatusBadRequest)
return
}
if event.Type == "ticket.created" {
log.Printf("new ticket %d", event.Data.Number)
}
})class KavchatWebhooksController < ActionController::API
def create
event = KAVCHAT.verify_webhook(request.raw_post, request.headers["Kavchat-Signature"])
Rails.logger.info("New ticket #{event.data.number}") if event.type == "ticket.created"
head :ok
rescue Kavchat::WebhookVerificationError => e
render plain: e.code, status: :bad_request
end
endVarsayılan tolerans 5 dakikadır (300 saniye). Her SDK'da bu değeri değiştirebilirsiniz; örneğin Node.js'te kavchat.verifyWebhook(body, header, { toleranceSeconds: 300 }) kullanabilirsiniz.
SDK olmadan
import crypto from 'node:crypto';
// header: "t=<unix seconds>,v1=<hex>", rawBody: the request body exactly as received
function verifyKavchatSignature(rawBody, header, secretKey) {
const parts = Object.fromEntries(String(header).split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
const expected = crypto.createHmac('sha256', secretKey).update(`${t}.${rawBody}`).digest('hex');
const given = Buffer.from(parts.v1 || '', 'hex');
return given.length === 32 && crypto.timingSafeEqual(given, Buffer.from(expected, 'hex'));
}Gönderim ve yeniden denemeler
- Sunucunuz 8 saniye içinde
2xxdurum koduyla yanıt verirse gönderim başarılı sayılır. - Başarısız gönderimler 5 saniye, 30 saniye ve 2 dakika sonra yeniden denenir: toplam dört deneme yapılır.
- Kullanıcı bundan etkilenmez: destek talebi her durumda yönetim panelindeki destek talebi kutusuna kaydedilir ve talebi her zaman Sunucu API'si üzerinden okuyabilirsiniz.
- Sunucunuz isteği alır ancak 8 saniyeden geç yanıt verirse istek tekrar gönderilir. Yinelenen kayıtları elemek için destek talebinin
iddeğerini kullanın.