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. 1

    Webhook-u qurun

    App-ın «Webhook-lar» bölməsində:

    1. URL yazın. Ünvan https:// ilə başlamalı və internetdən açıq olmalıdır: localhost, 10.x, 192.168.x kimi daxili ünvanlar qəbul olunmur, yönləndirmə izlənmir.
    2. 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.
    3. 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.

  2. 2

    Ünvanı təsdiqləyin

    URL-i saxlayanda Clomni ora { "type": "webhook.verify", "challenge": "c2b1f0a8e7d94c3b" } göndərir. Serveriniz 5 saniyə ərzində 200 və 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. 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 (status delivered, read, failed oldu): 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.uninstalled gəlir.

  4. 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?" }
    }

    id webhook-id başlığı ilə eynidir, dublikatı tanımaq üçün işlədin. data sinxron endpoint-ləri ilə eyni formadadır. conversation.updated və conversation.status_changed-də changed_attributes də olur. Public app-da zərfdə installation_id də var.

  5. 5

    İmzanı yoxlayın

    Yoxlanmayan webhook-u hər kəs saxta göndərə bilər.

    1. Gövdəni xam halda oxuyun, JSON-u parse edib yenidən yazmayın.
    2. İmzalanan mətn: {webhook-id}.{webhook-timestamp}.{xam gövdə}.
    3. Açar: secret-in whsec_-dən sonrakı hissəsi, base64-dən açılmış.
    4. HMAC-SHA256 hesablayın, base64 ilə yazın.
    5. 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.
    6. webhook-timestamp 5 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-id evt_2Wq8mX4vT9kLpR3n, webhook-timestamp 1794153600, 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. 6

    Çatdırılma və təkrar

    • Server 5 saniyədə 2xx qaytarmalıdır: hadisəni növbəyə qoyun, cavabı dərhal verin.
    • 2xx gə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-id ilə atın), sıra zəmanətli deyil (occurred_at ilə sıralayın).
    • message.updated failed səbəbini vermir: mesajı oxuyub content_attributes.external_error-a baxın.
  7. 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.test tipli 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əri GET /v1/events ilə eyni id-lərlə götürün. Lokal sınaq üçün ngrok və ya Cloudflare Tunnel kimi tunel ilə HTTPS ünvan alın.

  8. 8

    Webhook gəlmirsə

    1. Portalda webhook «təsdiqlənib» statusundadırmı? Deyilsə, server challenge-i 5 saniyədə qaytarmır.
    2. Mövzunun scope-u təsdiqlənibmi?
    3. Server 5 saniyədə 2xx verirmi? Jurnalda cavab koduna və səhvə baxın.
    4. URL internetdən açıqdırmı, https-dirmi, yönləndirmə yoxdurmu?
    5. Webhook söndürülübsə, yenidən aktiv edin.

Sənəd