Centru de ajutor
Chat API

Webhooks

Ultima actualizare:

Prezentare generală webhookuri

Webhookurile permit platformei ChatLab să vă notifice sistemele în momentul în care se întâmplă ceva în chatboturile dumneavoastră. În loc să interogați periodic Management API sau să exportați datele manual, înregistrați un endpoint HTTPS, iar ChatLab trimite către acesta un apel HTTP POST semnat, în timp real - atunci când un vizitator lasă un lead, trimite un formular de contact, evaluează o conversație, solicită un operator uman sau când se execută o acțiune AI.

Utilizări tipice:

  • trimiterea leadurilor noi direct în CRM-ul dumneavoastră în secunda în care sunt capturate
  • notificarea echipei în Slack atunci când un vizitator solicită Live Chat (chat pe viu)
  • transmiterea evaluărilor și a rezumatelor conversațiilor în propriul sistem de analiză
  • monitorizarea execuțiilor de tip AI action (acțiune AI) și alertarea în caz de erori

Disponibilitate: contul dumneavoastră trebuie să includă funcționalitatea Webhooks (Webhookuri).

Unde se configurează: în aplicația de administrare, deschideți Account settings -> Webhooks (Setări cont -> Webhookuri, chiar lângă secțiunea Management API). Webhookurile funcționează la nivel de cont - un singur endpoint poate primi evenimente de la toți boții dumneavoastră sau de la un bot anume selectat.

Configurarea unui endpoint

  1. Deschideți Account settings -> Webhooks (Setări cont -> Webhookuri) și apăsați pe Add endpoint (Adăugare endpoint).
  2. Completați formularul pentru endpoint:
    • Name (Nume) - o etichetă pentru referința dumneavoastră, de exemplu „Sincronizare CRM” sau „Alerte Slack”.
    • URL - adresa HTTPS către care ChatLab va trimite evenimentele prin POST.
    • Events (Evenimente) - selectați tipurile de evenimente pe care le primește acest endpoint (consultați catalogul de mai jos). Selectați doar ceea ce aveți nevoie; evenimentele cu volum mare, precum ai_action.executed, pot genera mult trafic.
    • Bot filter (Filtru bot) (opțional) - selectați un bot sau All bots (Toți boții) pentru toți boții din contul dumneavoastră.
    • Custom form filter (Filtru formular personalizat) (opțional) - direcționează trimiterile unui anumit formular personalizat către acest endpoint. Acesta filtrează doar evenimentul custom_form.submitted; toate celelalte evenimente la care v-ați abonat (leaduri, solicitări de contact, conversații, chat pe viu, acțiuni AI) sunt livrate indiferent de această setare.
  3. Trimiteți datele. Cheia secretă a endpointului (secret) este afișată o singură dată în fereastra de confirmare - copiați-o acum și stocați-o în siguranță. Veți avea nevoie de ea pentru verificarea semnăturilor (consultați secțiunea Securitate de mai jos). Textul în clar nu va mai putea fi recuperat ulterior.

Fiecare endpoint dispune, de asemenea, de:

  • Comutator activare/dezactivare - întrerupeți livrările fără a șterge endpointul. Endpointurile dezactivate ignoră silențios evenimentele (acestea nu sunt puse în coadă pentru mai târziu).
  • Send sample event (Trimitere eveniment de probă) - livrează o solicitare de test semnată către URL-ul dumneavoastră, permițându-vă să verificați receptorul de la un capăt la altul. Puteți alege tipul de eveniment și puteți edita valorile demonstrative înainte de trimitere, astfel încât handlerul dumneavoastră să proceseze date realiste. Testul sosește ca o livrare obișnuită, cu parametrul eventType corespunzător selecției dumneavoastră (sau ca webhook.test pentru o simplă verificare a conectivității).
  • Roll secret (Regenerare cheie secretă) - generează un secret nou și îl invalidează pe cel vechi. Utilizați această opțiune dacă bănuiți că secretul a fost compromis. Noul secret este afișat din nou o singură dată. Pentru o rotație controlată, întrerupeți endpointul, regenerați și copiați noul secret, actualizați receptorul, apoi reactivați endpointul și trimiteți un eveniment de probă. Evenimentele apărute în timpul pauzei nu sunt puse în coadă. Planificați această întrerupere înainte de rotație.
  • Delivery log (Jurnal de livrări) - o listă specifică fiecărui endpoint ce conține livrările recente, indicând marcajul temporal, tipul evenimentului, codul de stare HTTP returnat de serverul dumneavoastră și timpul de răspuns. Livrările eșuate și pauzele de protecție (circuit breaker) sunt vizibile aici. Jurnalul este păstrat timp de 14 zile.

Structura evenimentului (envelope)

Fiecare livrare este o solicitare HTTP POST cu antetul Content-Type: application/json. Corpul cererii respectă mereu aceeași structură cadru; obiectul data este specific tipului de eveniment:

{
  "eventId": "9f1c1c8e-6a2b-4b9e-9d2f-3f8a1e2b4c5d",
  "eventType": "lead.created",
  "timestamp": "2026-08-13T14:22:31Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": { }
}
  • eventId - identificator unic pentru fiecare eveniment. Utilizați-l pentru deduplicare dacă procesarea dumneavoastră trebuie să fie idempotentă.
  • eventType - unul dintre tipurile documentate mai jos; se trimite și în antetul X-ChatLab-Event.
  • timestamp - ora în format ISO 8601 UTC la care a fost pregătit pachetul de livrare.
  • botId / botName - botul căruia îi aparține evenimentul.
  • conversationId / sessionId - contextul conversației, dacă este cazul.

Catalog de evenimente

lead.created

Se declanșează atunci când un vizitator își trimite datele de contact - prin formularul de colectare a lead-urilor, pre-formularul de live chat sau un formular personalizat utilizat pentru colectarea lead-urilor.

{
  "eventId": "9f1c1c8e-6a2b-4b9e-9d2f-3f8a1e2b4c5d",
  "eventType": "lead.created",
  "timestamp": "2026-08-13T14:22:31Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "email": "jane.doe@example.com",
    "name": "Jane Doe",
    "phone": "+1 555 0123",
    "source": "LEAD_COLLECTION_FORM",
    "formCodeName": "lead_form",
    "formName": "Lead form",
    "fields": [
      {"name": "email", "value": "jane.doe@example.com", "type": "email"},
      {"name": "company", "value": "Acme Inc.", "type": "text"},
      {"name": "topics", "value": ["Billing", "Delivery"], "type": "multichoice"},
      {"name": "attachment", "value": "https://api.chatlab.com/aichat/customform/download?key=...&token=...", "type": "file"}
    ],
    "pageUrl": "https://acme.com/pricing"
  }
}
  • source - modul în care au fost capturate datele de contact: LEAD_COLLECTION_FORM (formular de colectare a lead-urilor), LIVE_CHAT_FORM (pre-formular de live chat), CONVERSATION (AI-ul a preluat datele în timpul conversației), ADMIN_DATA_UPDATE sau UPDATE_CLIENT_CONTEXT (editate din partea ChatLab). Trimiterile formularului de asistență umană nu declanșează niciodată acest eveniment - ele declanșează în schimb contact_form.submitted.
  • email, name, phone - datele de contact mapate în înregistrarea de lead.
  • Atunci când se utilizează un formular personalizat pentru colectarea lead-urilor, fiecare câmp definit în acel formular este inclus în fields, în ordinea din formular, iar formCodeName / formName identifică formularul. În cazul formularului clasic de lead, ambele sunt null, iar fields este un array gol.
  • Fiecare intrare din fields este de forma {name, value, type}. name este denumirea tehnică a câmpului, stabilă la modificările etichetelor - folosiți-o pentru maparea în CRM-ul dumneavoastră.
  • Pentru câmpurile multichoice, value este un array cu opțiunile selectate. Câmpurile de tip casetă de selectare sunt intrări individuale cu valori "true" / "false".
  • Pentru câmpurile file, value este un link de descărcare către fișierul încărcat; webhook-ul nu conține niciodată conținutul fișierelor.
  • pageUrl - pagina pe care se afla vizitatorul în momentul trimiterii.

contact_form.submitted

Se declanșează atunci când un vizitator trimite formularul de contact pentru asistență umană sau un formular personalizat utilizat pentru contact uman.

{
  "eventId": "3a7b9c2d-1e4f-4a6b-8c0d-5e2f7a9b1c3d",
  "eventType": "contact_form.submitted",
  "timestamp": "2026-08-13T14:25:02Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "email": "jane.doe@example.com",
    "message": "I need help with my last invoice.",
    "source": "CUSTOM_FORM",
    "formCodeName": "contact_form",
    "formName": "Contact form",
    "fields": [
      {"name": "email", "value": "jane.doe@example.com", "type": "email"},
      {"name": "order_number", "value": "A-10293", "type": "text"},
      {"name": "message", "value": "I need help with my last invoice.", "type": "textarea"}
    ]
  }
}
  • email - adresa pe care a lăsat-o vizitatorul și la care echipa dumneavoastră de asistență ar trebui să răspundă.
  • source - CUSTOM_FORM atunci când un formular personalizat susține formularul de contact, CONTACT_FORM pentru cel integrat.
  • formCodeName / formName - identifică formularul personalizat din spatele solicitării; ambele sunt null pentru formularul integrat.
  • Atunci când se utilizează un formular personalizat, fiecare câmp definit în acel formular este inclus în fields (același format {name, value, type} ca la lead.created). În cazul formularului de contact integrat, sunt completate doar email și message, fields este un array gol, iar identificatorii formularului sunt null.
  • message - câmpul de mesaj mapat sau toate valorile completate concatenate împreună atunci când formularul nu definește niciun câmp de mesaj.

custom_form.submitted

Se declanșează pentru fiecare trimitere de formular personalizat, indiferent de scopul formularului. Rețineți că formularele al căror scop este colectarea de lead-uri sau contactul uman declanșează și evenimentul lor dedicat lead.created / contact_form.submitted - abonați-vă la unul sau la celălalt în funcție de faptul dacă doriți vizualizarea generică sau cea specializată, pentru a evita crearea de acțiuni operaționale duplicate. Cele două familii de evenimente au ID-uri de eveniment diferite; combinația de conversationId și timestamp nu reprezintă un identificator fiabil pentru trimitere.

{
  "eventId": "6c1d8e3f-2a5b-4c7d-9e0f-1a4b6c8d0e2f",
  "eventType": "custom_form.submitted",
  "timestamp": "2026-08-13T14:27:45Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "formCodeName": "warranty_claim",
    "formName": "Warranty claim",
    "fields": [
      {"name": "order_number", "value": "A-10293", "type": "text"},
      {"name": "issue", "value": "Damaged on arrival", "type": "textarea"},
      {"name": "photo", "value": "https://api.chatlab.com/aichat/customform/download?key=...&token=...", "type": "file"}
    ],
    "purpose": "STANDALONE"
  }
}
  • formCodeName - numele tehnic stabil al formularului, care rămâne neschimbat atunci când redenumiți formularul; folosiți-l pentru a direcționa trimiterile în propriul sistem. formName este eticheta vizibilă afișată vizitatorilor.
  • fields utilizează aceleași intrări {name, value, type} ca lead.created: valorile câmpurilor multichoice sunt array-uri, iar valorile fișierelor sunt linkuri de descărcare.
  • purpose - STANDALONE, LEAD_COLLECTION sau HUMAN_CONTACT, în funcție de modul în care formularul este conectat la chatbot.

conversation.started

Se declanșează atunci când un vizitator trimite primul mesaj dintr-o conversație nouă.

{
  "eventId": "8e2f0a4b-3c6d-4e8f-a1b2-2c5d7e9f1a3b",
  "eventType": "conversation.started",
  "timestamp": "2026-08-13T14:20:11Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "firstMessage": "Do you ship to Canada?",
    "chatSource": "WIDGET",
    "byAdmin": false,
    "countryCode": "PL",
    "ipAddress": "83.12.44.7"
  }
}
  • firstMessage - textul exact al mesajului inițial al vizitatorului. Este null dacă a fost deschisă o conversație fără conținut de mesaj.
  • chatSource - canalul prin care a sosit conversația: WIDGET, WHATSAPP, MESSENGER, VOICE, VOICE_PHONE, API, BOOKING, AIRBNB sau IDOBOOKING.
  • byAdmin - true atunci când conversația provine din previzualizarea chatbotului din panoul de administrare ChatLab, și nu de la un vizitator real. Folosiți această proprietate pentru a exclude propriile conversații de test din CRM.
  • countryCode - codul de țară ISO determinat pe baza adresei IP a vizitatorului; null atunci când nu a putut fi determinat.
  • ipAddress - adresa IP a vizitatorului, așa cum a fost detectată de ChatLab; null când nu este disponibilă. Tratați această valoare drept date cu caracter personal în conformitate cu GDPR și stocați-o doar dacă aveți un temei legal.

conversation.rated

Se declanșează atunci când un vizitator trimite o evaluare pozitivă sau negativă a conversației (consultați Evaluarea conversației).

{
  "eventId": "1b4c6d8e-5f0a-4b2c-8d3e-4f7a9b1c3d5e",
  "eventType": "conversation.rated",
  "timestamp": "2026-08-13T14:31:09Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "rating": "POSITIVE"
  }
}
  • rating - POSITIVE sau NEGATIVE. Eliminarea unei evaluări nu declanșează evenimentul, astfel că nu veți primi niciodată o valoare neutră.

conversation.summarized

Se declanșează atunci când ChatLab generează un rezumat al unei conversații finalizate.

{
  "eventId": "4d7e9f1a-6b2c-4d4e-9f0a-5b8c0d2e4f6a",
  "eventType": "conversation.summarized",
  "timestamp": "2026-08-13T14:45:00Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "summary": "Visitor asked about shipping to Canada and delivery times. The bot confirmed availability and quoted 5-7 business days. Visitor left satisfied.",
    "language": "en_US"
  }
}
  • summary - textul rezumatului generat. Generarea rezumatului este asincronă și depinde de configurația de rezumare a botului și de programul de procesare; nu vă bazați pe un interval de timp fix pentru livrare.
  • language - setarea regională (locale) internă a botului transmisă către procesul de rezumare, de exemplu en_US; nu presupuneți că aceasta coincide cu limba utilizată de vizitator în conversație.

client.summarized

Se declanșează atunci când ChatLab actualizează profilul AI al unui client. Profilul este reconstruit pe baza profilului anterior plus rezumatul conversației care tocmai s-a încheiat și poate fi generat după conversation.summarized. Ordinea de livrare nu este garantată. Datele transmise includ o adresă de e-mail atunci când este cunoscută, însă ChatLab poate gestiona și clienți fără adresă de e-mail.

{
  "eventId": "b5d8f1a3-7c2e-4d9b-a6f0-1e3c5a7b9d2f",
  "eventType": "client.summarized",
  "timestamp": "2026-08-18T09:12:04Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "clientEmail": "jane.doe@example.com",
    "client": {
      "email": "jane.doe@example.com",
      "name": "Jane Doe",
      "phone": "+1 555 0123",
      "countryCode": "PL",
      "ipAddress": "83.12.44.7"
    },
    "clientSummary": "Returning customer interested in international shipping. Asked about delivery times to Canada twice and about return costs once."
  }
}
  • clientEmail - identificatorul utilizat pentru corelarea clientului cu propriul dumneavoastră CRM. Este null pentru vizitatorii anonimi care nu au lăsat niciodată o adresă, iar evenimentul se declanșează în continuare și pentru aceștia - ignorați aceste livrări dacă integrarea dumneavoastră se bazează pe e-mail.
  • client - datele de contact pe care ChatLab le deține pentru această persoană: email, name, phone, countryCode și ipAddress. Fiecare cheie este întotdeauna prezentă; valorile necunoscute sunt null.
  • clientSummary - textul complet al profilului ca text simplu, nu sub formă de diferențe (diff). Acesta înlocuiește rezumatul precedent, așadar salvați-l prin suprascriere, nu prin adăugare la final.
  • Profilul este reconstruit doar pentru boții cu funcția de memorie a chatului activată și doar pentru conversațiile care au fost inactive suficient timp pentru a fi rezumate - așteptați-vă la acest eveniment la câteva minute după încheierea conversației, nu imediat.

live_chat.requested

Se declanșează atunci când AI-ul transferă conversația către Live Chat (chat pe viu), fie pentru că vizitatorul a cerut un operator uman, fie pentru că botul a stabilit că este nevoie de o intervenție umană.

{
  "eventId": "7a0b2c4d-8e3f-4a5b-b0c1-6d9e1f3a5b7c",
  "eventType": "live_chat.requested",
  "timestamp": "2026-08-13T14:33:20Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "requestedBy": "AI"
  }
}
  • requestedBy - în prezent întotdeauna AI, deoarece transferul este inițiat mereu prin acțiunea de live chat a botului, inclusiv atunci când vizitatorul îl solicită explicit prin cuvinte. Tratați-l ca pe un enum deschis: gestionați valorile necunoscute în loc să verificați strict existența valorii AI.
  • Acest eveniment acoperă acțiunea AI-ului de a solicita transferul, nu toate modalitățile prin care un vizitator poate deschide o sesiune de live chat. El nu dovedește că un operator s-a alăturat conversației.

live_chat.started

Se declanșează atunci când sesiunea de live chat este creată după trimiterea formularului de transfer de către vizitator. Acest lucru se întâmplă înainte ca un operator să se alăture sau să răspundă efectiv; nu considerați acest eveniment drept o dovadă a intervenției umane.

{
  "eventId": "0c3d5e7f-9a4b-4c6d-a1b2-7e0f2a4b6c8d",
  "eventType": "live_chat.started",
  "timestamp": "2026-08-13T14:33:55Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {}
}
  • data este lăsat gol în mod intenționat. Toate informațiile de care aveți nevoie se află în structura de bază (envelope): botId identifică chatbotul, iar conversationId / sessionId leagă evenimentul de conversație. Este posibil să îl primiți fără un eveniment live_chat.requested anterior, de exemplu atunci când vizitatorul a utilizat elementul de control pentru live chat din widget.

live_chat.ended

Se declanșează atunci când sesiunea de live chat se încheie.

{
  "eventId": "2e5f7a9b-0c5d-4e7f-b2c3-8f1a3b5c7d9e",
  "eventType": "live_chat.ended",
  "timestamp": "2026-08-13T14:52:41Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "durationSeconds": 1126
  }
}
  • durationSeconds - durata scursă a sesiunii de live chat, calculată de la crearea sesiunii, inclusiv timpul de așteptare pentru un operator. Câmpul este omis în cazul rar în care o sesiune se încheie fără să fi fost vreodată pornită.

ai_action.executed

Se declanșează de fiecare dată când botul execută o acțiune AI - un apel către o integrare gestionată sau o funcție API personalizată. Acesta este un eveniment cu un volum ridicat: un bot activ de e-commerce poate executa sute de acțiuni pe zi, iar o singură replică a vizitatorului poate declanșa mai multe. Abonați-vă la acesta pe un endpoint dedicat sau asigurați-vă că sistemul dumneavoastră de recepție poate prelua acest volum.

{
  "eventId": "5f8a0b2c-1d6e-4f8a-c3d4-9a2b4c6d8e0f",
  "eventType": "ai_action.executed",
  "timestamp": "2026-08-13T14:21:03Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "actionName": "search_products",
    "status": "SUCCESS",
    "durationMs": 842,
    "errorMessage": null
  }
}
  • actionName - numele acțiunii executate, așa cum o identifică AI-ul, de exemplu search_products pentru o integrare gestionată sau denumirea atribuită unei acțiuni API personalizate.
  • status - SUCCESS sau ERROR.
  • durationMs - durata acțiunii, în milisecunde. Este utilă pentru detectarea unei integrări lente înainte ca vizitatorii să semnaleze probleme.
  • errorMessage - motivul erorii, completat doar atunci când status este ERROR; în rest, valoarea este null.

webhook.test

Trimis de butonul Send sample event (Trimite eveniment de probă) atunci când efectuați o simplă verificare a conectivității. Este semnat exact ca un eveniment real.

{
  "eventId": "9b2c4d6e-3f8a-4b0c-d5e6-0b3c5d7e9f1a",
  "eventType": "webhook.test",
  "timestamp": "2026-08-13T14:10:00Z",
  "botId": 1234,
  "botName": "Support Bot",
  "conversationId": "conv_a1b2c3",
  "sessionId": "sess_x9y8z7",
  "data": {
    "message": "Test delivery from ChatLab"
  }
}
  • message - text fix, mereu identic. Câmpurile structurii de bază poartă valori demonstrative, așadar nu tratați niciodată o livrare webhook.test ca date reale.
  • Acesta este singurul tip de eveniment la care nu vă puteți abona pe un endpoint: este trimis la cerere din panoul de administrare și ajunge întotdeauna pe endpoint-ul pe care ați dat clic, indiferent de evenimentele pe care le ascultă acesta.

Securitate: verificarea livrărilor

Fiecare livrare conține patru anteturi (headers):

Antet Valoare
X-ChatLab-Signature sha256=<hex hmac> - semnătura HMAC-SHA256 a sarcinii utile (payload)
X-ChatLab-Timestamp Timpul Unix în secunde când livrarea a fost semnată
X-ChatLab-Event Tipul de eveniment, de ex. lead.created
X-ChatLab-Delivery ID-ul unic de livrare, identic cu eventId din corpul cererii

Semnătura este calculată ca HMAC-SHA256 pe șirul {timestamp}.{rawBody} utilizând cheia secretă a endpoint-ului dumneavoastră, unde {timestamp} este valoarea din X-ChatLab-Timestamp, iar {rawBody} este corpul brut, neprelucrat, al cererii. Verificați întotdeauna pe baza octeților bruți - reserializarea JSON-ului parsat va schimba secvența de octeți și va invalida semnătura.

Pentru a vă proteja împotriva atacurilor de tip replay, respingeți livrările ale căror valori X-ChatLab-Timestamp sunt mai vechi de 5 minute.

Node.js

const crypto = require('crypto');

function verifyChatLabSignature(req, secret) {
    const signature = req.headers['x-chatlab-signature'];
    const timestamp = req.headers['x-chatlab-timestamp'];
    if (typeof signature !== 'string' || typeof timestamp !== 'string') return false;
    if (!/^\d+$/.test(timestamp) || !Number.isSafeInteger(Number(timestamp))) return false;
    if (!Buffer.isBuffer(req.rawBody)) return false;

    // Reject stale deliveries (older than 5 minutes)
    const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
    if (ageSeconds > 300) return false;

    // rawBody must be the raw request body bytes, not re-serialized JSON.
    // With Express: app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }))
    const expected = 'sha256=' + crypto
        .createHmac('sha256', secret)
        .update(timestamp + '.')
        .update(req.rawBody)
        .digest('hex');

    const a = Buffer.from(signature);
    const b = Buffer.from(expected);
    return a.length === b.length && crypto.timingSafeEqual(a, b);
}

PHP

<?php
function verifyChatLabSignature(string $secret): bool
{
    $signature = $_SERVER['HTTP_X_CHATLAB_SIGNATURE'] ?? '';
    $timestamp = $_SERVER['HTTP_X_CHATLAB_TIMESTAMP'] ?? '';
    if ($signature === '' || $timestamp === '' || !ctype_digit($timestamp)) {
        return false;
    }

    // Reject stale deliveries (older than 5 minutes)
    if (abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    $rawBody = file_get_contents('php://input');
    $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);

    return hash_equals($expected, $signature);
}

Dacă verificarea eșuează, răspundeți cu codul 401 și respingeți datele primite. Nu procesați niciodată livrări neverificate - oricine descoperă adresa URL poate trimite cereri de tip POST cu conținut JSON arbitrar către aceasta.

Comportamentul de livrare

Înțelegeți aceste reguli de livrare înainte de a construi pe baza webhook-urilor:

  • Răspundeți rapid. Punctul dumneavoastră final (endpoint) trebuie să răspundă în decurs de 3 secunde, altfel livrarea este considerată eșuată. Verificați semnătura, adăugați evenimentul într-o coadă de așteptare persistentă și confirmați cu 2xx în acest interval. Executați apelurile CRM mai lente și procesarea de business în mod asincron.
  • Fire-and-forget, cel mult o dată (at-most-once). Punctele finale active care se potrivesc primesc cel mult o încercare de livrare - nu există reîncercări. Punctele finale întrerupte și circuitele deschise pot suprima chiar și acea încercare. Dacă punctul dumneavoastră final este indisponibil, depășește timpul de așteptare sau returnează un status diferit de 2xx, acel eveniment este pierdut și nu va fi relivrat. Webhook-urile sunt notificări, nu un depozit de date replicat: utilizați Bot Talk API conversation endpoints sau exporturile de lead-uri pentru a reconcilia datele păstrate, acolo unde este suportat. Management API acoperă setările botului și datele de utilizare, nu o arhivă completă a evenimentelor. Unele evenimente nu pot fi reconstituite prin aceste interfețe.
  • Circuit breaker. După 5 livrări eșuate consecutive pentru o pereche punct final/bot, livrările pentru acea pereche se întrerup timp de 5 minute. Evenimentele apărute în timpul pauzei sunt ignorate, iar jurnalul de livrare afișează intrări CIRCUIT_OPEN pentru a identifica încercările suprimate. Livrările omise din cauza unui circuit deschis nu sunt luate în calcul pentru dezactivarea automată.
  • Dezactivare automată. Verificarea rulează în momentul în care o livrare eșuează, niciodată pe baza unui cronometru. Dacă o livrare eșuează și nu a existat nicio livrare reușită timp de 7 zile - calculate de la ultima reușită sau de la data creării punctului final dacă nu a reușit niciodată - punctul final este dezactivat și primiți o notificare prin e-mail. Un singur cod 2xx în orice moment resetează această perioadă. Un punct final care nu primește trafic nu este dezactivat niciodată, deoarece nimic nu eșuează. Reactivați-l din Account settings (Setări cont) odată ce receptorul dumneavoastră este remediat; contorul de eșecuri și marcajul de dezactivare automată sunt resetate atunci când îl reactivați, iar evenimentele pierdute în perioada în care a fost oprit nu sunt recuperate retroactiv.
  • 410 Gone. Dacă punctul dumneavoastră final răspunde cu HTTP 410 Gone, ChatLab îl dezactivează imediat. Utilizați acest lucru pentru a scoate din uz în mod programatic un punct final de pe partea de recepție.
  • Idempotență. Livrările duplicate nu sunt preconizate în condiții normale de funcționare, dar dacă procesarea dumneavoastră trebuie să fie strict idempotentă, deduplicați după eventId (disponibil și în antetul X-ChatLab-Delivery).

Ordinea livrării nu este garantată. Salvați ID-ul evenimentului și asigurați idempotența procesării de business. Mențineți propriile politici de retenție și controale de acces pentru încărcăturile utile (payloads) ale webhook-urilor, care pot conține date cu caracter personal și linkuri de descărcare a fișierelor.

Exemplele de semnătură utilizează Node.js crypto și funcția hash_equals din PHP. Capturați corpul brut al cererii (raw request body) înainte de a-l analiza.

Limite

  • Până la 10 puncte finale de webhook per cont.
  • Retenția jurnalului de livrare: 14 zile. Intrările mai vechi sunt eliminate automat.

Articole conexe