App webhook-ları və hadisələr: qurmaq, mövzular, imzanın yoxlanması
Son yeniləmə: 11 noyabr 2026 · 8 dəq oxunur
Webhook Clomni-də hadisə baş verəndə serverinizə göndərilən HTTP sorğusudur: müştəri yazan kimi message.created gəlir, siyahını hər dəqiqə yoxlamaq lazım olmur. Bu məqalə developer.clomni.ai-dakı app-ın webhook-u haqqındadır. Paneldəki workspace webhook-u (Parametrlər › İş sahəsi parametrləri › İnteqrasiyalar › Webhook) ayrıdır, onun üçün «Webhook qurmaq» məqaləsinə baxın.
Addımlar
-
1
Webhook-u qurun
App-ın «Webhook-lar» bölməsində:
- URL yazın. Ünvan
https://ilə başlamalı və internetdən açıq olmalıdır:localhost,10.x,192.168.xkimi daxili ünvanlar qəbul olunmur, yönləndirmə izlənmir. - Mövzuları seçin. Daxili app-da yalnız təsdiqlənmiş scope-a uyğun mövzu seçilir, public app-da istənən scope kifayətdir.
- Secret-i saxlayın:
whsec_ilə başlayır və yalnız bir dəfə göstərilir.
Bir app-ın bir webhook ünvanı olur.
- URL yazın. Ünvan
-
2
Ünvanı təsdiqləyin
URL-i saxlayanda Clomni ora
{ "type": "webhook.verify", "challenge": "c2b1f0a8e7d94c3b" }göndərir. Serveriniz 5 saniyə ərzində200və gövdədə həmin dəyərlə cavab verməlidir:{ "challenge": "c2b1f0a8e7d94c3b" }. Cavab gəlməsə, webhook «təsdiqlənməyib» qalır və hadisə göndərilmir: serveri düzəldib portalda yenidən saxlayın. Bu sorğu da imzalanır. -
3
Mövzular və lazım olan scope
conversation.created,conversation.updated(etiket, prioritet, atribut),conversation.status_changed,conversation.assigned:conversations:read.message.created(gələn, gedən, daxili qeyd),message.updated(statusdelivered,read,failedoldu):messages:read.contact.created,contact.updated:contacts:read.
Scope app-dan silinsə, ona uyğun mövzu da dayanır. Public app-da administrator app-ı ləğv edəndə mövzudan asılı olmayaraq
app.uninstalledgəlir. -
4
Sorğunun forması
Başlıqlar:
Content-Type: application/json,User-Agent: Clomni-Webhooks/1.0,webhook-id,webhook-timestamp,webhook-signature. Gövdə:{ "id": "evt_2Wq8mX4vT9kLpR3n", "type": "message.created", "api_version": "v1", "workspace_id": 42, "occurred_at": "2026-11-08T16:00:00.412Z", "data": { "id": 51234, "conversation_number": 1042, "direction": "incoming", "content": "Salam, sifarişim nə vaxt çatacaq?" } }idwebhook-idbaşlığı ilə eynidir, dublikatı tanımaq üçün işlədin.datasinxron endpoint-ləri ilə eyni formadadır.conversation.updatedvəconversation.status_changed-dəchanged_attributesdə olur. Public app-da zərfdəinstallation_iddə var. -
5
İmzanı yoxlayın
Yoxlanmayan webhook-u hər kəs saxta göndərə bilər.
- Gövdəni xam halda oxuyun, JSON-u parse edib yenidən yazmayın.
- İmzalanan mətn:
{webhook-id}.{webhook-timestamp}.{xam gövdə}. - Açar: secret-in
whsec_-dən sonrakı hissəsi, base64-dən açılmış. - HMAC-SHA256 hesablayın, base64 ilə yazın.
webhook-signature-da boşluqla ayrılmış bir neçəv1,<imza>ola bilər: biri sabit vaxtlı müqayisədə uyğun gəlsə, düzgündür.webhook-timestamp5 dəqiqədən köhnədirsə, rədd edin.
import crypto from "node:crypto"; function verifyWebhook(rawBody, headers, secret) { const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = headers["webhook-signature"]; if (!id || !ts || !sigs || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const expected = crypto.createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest(); return sigs.split(" ").some((part) => { const [v, sig] = part.split(","); const given = Buffer.from(sig || "", "base64"); return v === "v1" && given.length === expected.length && crypto.timingSafeEqual(given, expected); }); }import base64, hashlib, hmac, time def verify_webhook(raw_body: bytes, headers, secret: str) -> bool: msg_id, ts, sigs = headers.get("webhook-id"), headers.get("webhook-timestamp"), headers.get("webhook-signature") if not msg_id or not ts or not sigs or abs(time.time() - int(ts)) > 300: return False key = base64.b64decode(secret.removeprefix("whsec_")) expected = base64.b64encode(hmac.new(key, f"{msg_id}.{ts}.".encode() + raw_body, hashlib.sha256).digest()).decode() return any(p.partition(",")[0] == "v1" and hmac.compare_digest(p.partition(",")[2], expected) for p in sigs.split(" "))Sınaq dəyərləri: secret
whsec_aSGonRLU/DDLGZkKrR6fe7DCdyC22NiWOb8feJg/O6k=,webhook-idevt_2Wq8mX4vT9kLpR3n,webhook-timestamp1794153600, gövdə{"id":"evt_2Wq8mX4vT9kLpR3n","type":"message.created","api_version":"v1","workspace_id":42,"occurred_at":"2026-11-08T16:00:00Z","data":{"id":51234}}. Nəticəv1,hJufoXNHJ1dUd1zcZdtiPLkp9Px9eS+ZIUi9odi4Cvk=olmalıdır. Bu secret yalnız sənəd üçündür, vaxt köhnədir: sınaqda 5 dəqiqə yoxlamasını söndürün. -
6
Çatdırılma və təkrar
- Server 5 saniyədə
2xxqaytarmalıdır: hadisəni növbəyə qoyun, cavabı dərhal verin. 2xxgəlməsə, Clomni 1 dəqiqə, 5 dəqiqə, 30 dəqiqə, 2 saat, 6 saat, 12 saat və 24 saat sonra yenidən göndərir.- 3 gün ardıcıl heç bir çatdırılma alınmasa, webhook söndürülür və app sahibinə e-poçt gedir. Serveri düzəldib portalda yenidən aktiv edin.
- Eyni hadisə iki dəfə gələ bilər (
webhook-idilə atın), sıra zəmanətli deyil (occurred_atilə sıralayın). message.updatedfailedsəbəbini vermir: mesajı oxuyubcontent_attributes.external_error-a baxın.
- Server 5 saniyədə
-
7
Portalda yoxlayın
«Webhook-lar» bölməsində son 30 günün çatdırılma jurnalı (mövzu, status, cavab kodu, müddət, səhv) var. «Test göndər» dərhal
webhook.testtipli sınaq hadisəsi göndərir, «Yenidən göndər» uğursuz çatdırılmanı təkrarlayır. Secret-i dəyişəndən sonra 24 saat başlıqda iki imza (köhnə və yeni) olur. Server uzun dayanıbsa, qaçan hadisələriGET /v1/eventsilə eyniid-lərlə götürün. Lokal sınaq üçün ngrok və ya Cloudflare Tunnel kimi tunel ilə HTTPS ünvan alın. -
8
Webhook gəlmirsə
- Portalda webhook «təsdiqlənib» statusundadırmı? Deyilsə, server
challenge-i 5 saniyədə qaytarmır. - Mövzunun scope-u təsdiqlənibmi?
- Server 5 saniyədə
2xxverirmi? Jurnalda cavab koduna və səhvə baxın. - URL internetdən açıqdırmı,
https-dirmi, yönləndirmə yoxdurmu? - Webhook söndürülübsə, yenidən aktiv edin.
- Portalda webhook «təsdiqlənib» statusundadırmı? Deyilsə, server