Webhook
כשמשתמש פותח פנייה מהצ'אט, Kavchat יכול לשלוח אותה מיד למערכת שלכם: מערכת התמיכה, ה-CRM או בוט ב-Slack. כל שליחה חתומה במפתח הסודי שלכם.
ה-webhook כלול בחבילת הפרימיום.
הגדרה
- יוצרים בשרת כתובת HTTPS שמקבלת
POSTעם גוף JSON. - בלוח הבקרה נכנסים להגדרות ומזינים את הכתובת תחת Webhook.
- מאמתים את החתימה של כל בקשה, כמו שמוסבר בהמשך, ועונים בכל סטטוס
2xx.
הכתובת חייבת להתחיל ב-https:// ולהפנות לכתובת ציבורית. כתובות ברשתות פרטיות נחסמות.
תוכן ההודעה
Kavchat שולח שני סוגי אירועים: ticket.created לפנייה שנשלחה מטופס הפנייה, ו-lead.created לפרטים שמבקר השאיר בשלב ליד בתסריט. השדה data מכיל את הפנייה או הליד, באותו מבנה שמחזיר ה-Server API, כולל תמליל השיחה.
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 הוא השדה sub מטוקן הזהות כשהמשתמש זוהה, ואחרת null. גם name, email, phone ושדות הנושא יכולים להיות null.
לידים ופרטים אישיים
תסריט יכול לשאול את המבקר שאלות (שם, טלפון, אימייל, מספר או טקסט חופשי), לבדוק כל תשובה ולשמור אותה במשתנה. שלב ליד שולח את הפרטים האלה לתיבת הלידים בלוח הבקרה. באירוע lead.created, השדה data.variables מכיל את כל הערכים שנאספו, לפי שם המשתנה. הערכים הנפוצים מופיעים גם בשדות data.name, data.email ו-data.phone. מספרי טלפון מגיעים בפורמט בינלאומי (E.164).
הפרטים האלה הם מידע אישי. Kavchat שומר אותם רק בליד, בפנייה או בשיחה עם הנציג שאליהם הם שייכים, ולעולם לא בדוחות או ביומנים. לידים נמחקים בסוף תקופת השמירה שבחרתם (30 יום בתוכנית החינמית, ובפרימיום אפשר גם לשמור בלי למחוק). אפשר למחוק ליד בודד בכל רגע. אם אתם אוספים פרטים, ציינו זאת במדיניות הפרטיות שלכם. בשורת ההסכמה של שלב הליד, הסבירו למבקרים מה תעשו בפרטים.
הכותרת Kavchat-Signature
הכותרת נראית כך: t=<unix seconds>,v1=<hex>, כאשר:
tהוא זמן השליחה, בשניות Unix.v1הואHMAC-SHA256(secretKey, "<t>.<raw body>")בקידוד hex. המפתח הוא המפתח הסודי המלא שלכם.
כדי לאמת, מחשבים את אותו HMAC על הגוף הגולמי, בדיוק כפי שהגיע, משווים בזמן קבוע, ודוחים בקשות ישנות מ-5 דקות. גוף שעבר פענוח והמרה חזרה ל-JSON לא יתאים לחתימה.
אימות עם ערכת פיתוח
verifyWebhook עושה את כל זה ומחזירה את האירוע מפוענח. בכישלון היא זורקת שגיאה עם קוד: malformed_header, timestamp_out_of_range, invalid_signature או invalid_payload. עונים עליהן ב-HTTP 400. התקנת הערכות מוסברת בזיהוי משתמשים.
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
endברירת המחדל היא 5 דקות (300 שניות). בכל ערכה אפשר לשנות אותה, למשל kavchat.verifyWebhook(body, header, { toleranceSeconds: 300 }) ב-Node.js.
בלי ערכת פיתוח
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'));
}שליחה וניסיונות חוזרים
- שליחה מצליחה כשהשרת שלכם עונה בסטטוס
2xxתוך 8 שניות. - שליחה שנכשלה נשלחת שוב אחרי 5 שניות, 30 שניות ו-2 דקות: ארבעה ניסיונות בסך הכל.
- המשתמש לא מרגיש בזה: הפנייה נשמרת בתיבת הפניות בלוח הבקרה בכל מקרה, ותמיד אפשר לקרוא אותה דרך ה-Server API.
- אם השרת שלכם קיבל את הבקשה אבל ענה אחרי יותר מ-8 שניות, היא תישלח שוב. השתמשו ב-
idשל הפנייה כדי לסנן כפילויות.