Webhook
Quando un utente apre un ticket dalla chat, Kavchat può inviarlo subito al tuo sistema: help desk, CRM o bot su Slack. Ogni invio è firmato con la tua chiave segreta.
Il webhook è incluso nel piano Premium.
Configurazione
- Crea sul tuo server un endpoint HTTPS che accetti richieste
POSTcon un corpo JSON. - Nel pannello di controllo, apri Impostazioni e inserisci l'URL alla voce Webhook.
- Verifica la firma di ogni richiesta, come spiegato qui sotto, e rispondi con un qualsiasi codice di stato
2xx.
L'URL deve iniziare con https:// e puntare a un indirizzo pubblico. Gli indirizzi su reti private vengono bloccati.
Contenuto del messaggio
Kavchat invia due tipi di eventi: ticket.created per un ticket inviato dal modulo di contatto e lead.created per i dati lasciati da un visitatore in un passaggio di raccolta lead dello script. Il campo data contiene il ticket o il lead nello stesso formato restituito dalla Server API, inclusa la trascrizione della conversazione.
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 corrisponde al campo sub del token di identità se l'utente è stato identificato, altrimenti è null. Anche name, email, phone e i campi relativi all'argomento possono essere null.
Lead e dati personali
Uno script può chiedere al visitatore nome, telefono, email, un numero o un testo libero, verificare ogni risposta e salvarla in una variabile. Un passaggio di raccolta lead invia questi dati alla casella dei lead nel pannello di controllo. Nell'evento lead.created, il campo data.variables contiene tutti i valori raccolti, associati al nome della variabile. I valori più comuni compaiono anche nei campi data.name, data.email e data.phone. I numeri di telefono sono in formato internazionale (E.164).
Queste informazioni sono dati personali. Kavchat le conserva solo nel lead, nel ticket o nella conversazione con l'operatore a cui appartengono, mai nei report o nei log. I lead vengono eliminati al termine del periodo di conservazione che hai scelto (30 giorni nel piano gratuito; con Premium puoi anche conservarli senza scadenza). Puoi eliminare un singolo lead in qualsiasi momento. Se raccogli dati personali, indicalo nella tua informativa sulla privacy. Nel testo del consenso del passaggio di raccolta lead, spiega ai visitatori come userai i loro dati.
L'header Kavchat-Signature
L'header ha questo formato: t=<unix seconds>,v1=<hex>, dove:
tè l'ora di invio, espressa in secondi Unix.v1èHMAC-SHA256(secretKey, "<t>.<raw body>")in formato esadecimale. La chiave è la tua chiave segreta completa.
Per verificare la firma, calcola lo stesso HMAC sul corpo grezzo, esattamente come è stato ricevuto, confronta le firme in tempo costante e rifiuta le richieste più vecchie di 5 minuti. Un corpo decodificato e poi riconvertito in JSON non corrisponderà alla firma.
Verifica con un SDK
verifyWebhook esegue tutte queste operazioni e restituisce l'evento decodificato. In caso di errore, genera un'eccezione con uno di questi codici: malformed_header, timestamp_out_of_range, invalid_signature o invalid_payload. Rispondi a questi errori con HTTP 400. L'installazione degli SDK è descritta in Identificazione degli utenti.
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
endLa tolleranza predefinita è di 5 minuti (300 secondi). Puoi modificarla in ogni SDK, ad esempio con kavchat.verifyWebhook(body, header, { toleranceSeconds: 300 }) in Node.js.
Senza 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'));
}Invio e nuovi tentativi
- L'invio va a buon fine quando il tuo server risponde con un codice di stato
2xxentro 8 secondi. - Se l'invio fallisce, viene ripetuto dopo 5 secondi, 30 secondi e 2 minuti: quattro tentativi in totale.
- Per l'utente non cambia nulla: il ticket viene comunque salvato nella casella dei ticket del pannello di controllo e puoi sempre leggerlo tramite la Server API.
- Se il tuo server ha ricevuto la richiesta ma ha risposto dopo più di 8 secondi, la richiesta verrà inviata di nuovo. Usa l'
iddel ticket per filtrare i duplicati.