Rehber menüsü

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

  1. Sunucunuzda JSON gövdeli POST isteklerini kabul eden bir HTTPS uç noktası oluşturun.
  2. Yönetim panelinde Ayarlar bölümünü açın ve adresi Webhook alanına girin.
  3. Her isteğin imzasını aşağıda açıklandığı gibi doğrulayın ve herhangi bir 2xx durum 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.

HTTP
POST /kavchat/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Kavchat-Webhook/1
Kavchat-Signature: t=1792915200,v1=5f2b7c...e91a
JSON
{
  "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.

Node.js
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;
  }
});
Python
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 "", 200
PHP
use 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);
}
Java
@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());
    }
}
.NET
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);
    }
});
Go
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)
	}
})
Ruby
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
end

Varsayı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

Node.js (no SDK)
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 2xx durum 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 id değerini kullanın.