Ohjekeskus
Chat API

Webhookit

Viimeksi päivitetty:

Webhookien yleiskatsaus

Webhookien avulla ChatLab voi ilmoittaa järjestelmillesi heti, kun chatboteissasi tapahtuu jotain. Sen sijaan, että kyselisit tietoja Management API:sta toistuvasti tai veisit dataa manuaalisesti, rekisteröit HTTPS-päätepisteen, johon ChatLab lähettää allekirjoitetun HTTP POST -pyynnön reaaliajassa - kun vierailija jättää liidin, lähettää yhteydenottolomakkeen, arvioi keskustelun, pyytää asiakaspalvelijaa tai kun tekoälytoiminto suoritetaan.

Tyypillisiä käyttötarkoituksia:

  • uusien liidien siirtäminen suoraan CRM-järjestelmääsi heti, kun ne on tallennettu
  • ilmoituksen lähettäminen tiimillesi Slackissa, kun vierailija pyytää Live Chatia
  • keskustelujen arviointien ja yhteenvetojen syöttäminen omaan analytiikkaasi
  • AI Action -suoritusten seuranta ja virheistä hälyttäminen

Saatavuus: webhookit ovat käytettävissä STANDARD-paketista alkaen (ominaisuus: Webhooks).

Missä määritys tehdään: avaa hallintapaneelissa Account settings -> Webhooks (Tilin asetukset -> Webhookit) (aivan Management API -osion vieressä). Webhookit ovat tilitasoisia - yksi päätepiste voi vastaanottaa tapahtumia kaikista boteistasi tai suodatetusta osajoukosta.

Päätepisteen määrittäminen

  1. Avaa Account settings -> Webhooks ja napsauta Create endpoint (Luo päätepiste).
  2. Täytä päätepisteen lomake:
    • Name (Nimi) - omaan käyttöön tarkoitettu tunniste, esim. "CRM-synkronointi" tai "Slack-hälytykset".
    • URL - HTTPS-osoite, johon ChatLab lähettää tapahtumat POST-pyyntöinä.
    • Events (Tapahtumat) - valitse, mitä tapahtumatyyppejä tämä päätepiste vastaanottaa (katso alla oleva luettelo). Valitse vain tarvitsemasi tapahtumat; paljon liikennettä tuottavat tapahtumat, kuten ai_action.executed, voivat aiheuttaa runsaasti liikennettä.
    • Bot filter (Bottisuodatin) (valinnainen) - rajaa päätepiste tiettyihin botteihin. Jätä tyhjäksi, jos haluat vastaanottaa tapahtumia kaikista tilisi boteista.
    • Custom form filter (Mukautetun lomakkeen suodatin) (valinnainen) - ohjaa tietyn mukautetun lomakkeen lähetykset tähän päätepisteeseen. Se rajaa vain tapahtumaa custom_form.submitted; kaikki muut tilaamasi tapahtumat (liidit, yhteydenottopyynnöt, keskustelut, live chat, tekoälytoiminnot) toimitetaan tästä asetuksesta riippumatta.
  3. Lähetä lomake. Päätepisteen salaisuus (secret) näytetään tasan kerran onnistumisikkunassa - kopioi se talteen heti ja säilytä sitä turvallisesti. Tarvitset sitä allekirjoitusten vahvistamiseen (katso Turvallisuus alla). Selväkielistä merkkijonoa ei voi hakea myöhemmin.

Jokaisella päätepisteellä on myös:

  • Ota käyttöön / poista käytöstä -kytkin (Enable/disable toggle) - keskeytä toimitukset poistamatta päätepistettä. Käytöstä poistetut päätepisteet hylkäävät tapahtumat hiljaisesti (niitä ei laiteta jonoon myöhempää varten).
  • Send sample event (Lähetä esimerkkitapahtuma) - toimittaa allekirjoitetun testipyynnön URL-osoitteeseesi, jotta voit varmistaa vastaanottimesi toiminnan alusta loppuun. Voit valita tapahtumatyypin ja muokata esimerkkipyyntöjen arvoja ennen lähettämistä, jotta käsittelijäsi näkee realistista dataa. Testi saapuu tavallisena toimituksena, jonka eventType vastaa valintaasi (tai muodossa webhook.test, jos kyseessä on pelkkä yhteyden tarkistus).
  • Roll secret (Uusi salaisuus) - luo uuden salaisuuden ja mitätöi vanhan. Käytä tätä, jos salaisuus on saattanut vuotaa. Uusi salaisuus näytetään jälleen vain kerran. Päivitä vastaanottimesi ennen salaisuuden uusimista, tai toimitusten allekirjoitusten vahvistus epäonnistuu järjestelmässäsi.
  • Delivery log (Toimitusloki) - päätepisteen viimeaikaisten toimitusten luettelo, joka sisältää aikaleiman, tapahtumatyypin, palvelimesi palauttaman HTTP-tilakoodin ja vasteajan. Epäonnistuneet toimitukset ja automaattisen virrankatkaisun (circuit breaker) tauot näkyvät täällä. Lokia säilytetään 14 päivää.

Tapahtumapaketti (Event envelope)

Jokainen toimitus on HTTP POST -pyyntö, jossa on Content-Type: application/json. Rungolla on aina sama perusrakenne; data-objekti määräytyy tapahtumatyypin mukaan:

{
  "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 - uniikki tunniste tapahtumaa kohden. Käytä sitä duplikaattien estämiseen, jos käsittelysi on oltava idempotenttia.
  • eventType - yksi alla kuvatuista tyypeistä; lähetetään myös X-ChatLab-Event-otsakkeessa.
  • timestamp - ISO 8601 UTC -aika, jolloin tapahtuma tapahtui.
  • botId / botName - botti, jolle tapahtuma kuuluu.
  • conversationId / sessionId - keskustelukonteksti, silloin kun se on sovellettavissa.

Tapahtumaluettelo

lead.created

Laukeaa, kun vierailija lähettää yhteystietonsa - liidien keruulomakkeen, live chatin esilomakkeen tai liidien keruuseen käytettävän mukautetun lomakkeen kautta.

{
  "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 - tapa, jolla yhteystiedot kerättiin: LEAD_COLLECTION_FORM (liidien keruulomake), LIVE_CHAT_FORM (live chatin esilomake), CONVERSATION (tekoäly poimi tiedot keskustelun aikana), ADMIN_DATA_UPDATE tai UPDATE_CLIENT_CONTEXT (muokattu ChatLabin puolella). Asiakaspalvelun yhteydenottolomakkeen lähetykset eivät koskaan laukaise tätä tapahtumaa - ne laukaisevat sen sijaan tapahtuman contact_form.submitted.
  • email, name, phone - liiditietueeseen yhdistetyt yhteystiedot.
  • Kun liidien keräämiseen käytetään mukautettua lomaketta, jokainen lomakkeessa määritetty kenttä sisältyy taulukkoon fields lomakkeen mukaisessa järjestyksessä, ja formCodeName / formName tunnistavat lomakkeen. Perinteisen liidilomakkeen kohdalla molemmat ovat null ja fields on tyhjä taulukko.
  • Jokainen fields-taulukon alkio on muotoa {name, value, type}. name on kentän tekninen nimi, joka pysyy samana otsikkomuutoksista riippumatta - käytä sitä kenttien yhdistämiseen (mapping) CRM-järjestelmässäsi.
  • Monivalintakentissä (multichoice) value on valittujen vaihtoehtojen taulukko (array). Valintaruutukentät ovat yksittäisiä kenttiä arvoilla "true" / "false".
  • Tiedostokentissä (file) value on ladatun tiedoston latauslinkki; webhook ei koskaan sisällä itse tiedoston sisältöä.
  • pageUrl - sivu, jolla vierailija oli lomakkeen lähettäessään.

contact_form.submitted

Laukeaa, kun vierailija lähettää asiakaspalvelun yhteydenottolomakkeen tai yhteydenottoon käytettävän mukautetun lomakkeen.

{
  "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 - osoite, jonka vierailija jätti ja johon tukitiimisi tulisi vastata.
  • source - CUSTOM_FORM, kun yhteydenottolomakkeen taustalla on mukautettu lomake, tai CONTACT_FORM, kun kyseessä on sisäänrakennettu lomake.
  • formCodeName / formName - pyynnön taustalla olevan mukautetun lomakkeen tunnisteet; molemmat ovat null sisäänrakennetulla lomakkeella.
  • Kun mukautettua lomaketta käytetään, jokainen kyseiseen lomakkeeseen määritetty kenttä sisältyy fields-taulukkoon (sama muoto {name, value, type} kuin tapahtumassa lead.created). Sisäänrakennetulla yhteydenottolomakkeella vain email ja message täytetään, fields on tyhjä taulukko ja lomaketunnisteet ovat null.
  • message - yhteydenottoviestiksi määritetty kenttä tai kaikkien täytettyjen kenttien yhdistetty teksti, jos lomakkeessa ei ole erillistä viestikenttää.

custom_form.submitted

Laukeaa kaikista mukautetun lomakkeen lähetyksistä lomakkeen käyttötarkoituksesta riippumatta. Huomaa, että lomakkeet, joiden käyttötarkoitus on liidien keruu tai ihmisen tavoittaminen, laukaisevat myös niille omistetun tapahtuman lead.created / contact_form.submitted - tilaa jompikumpi sen mukaan, haluatko yleisen vai erikoistuneen näkymän, ja poista duplikaatit yhdistelmällä conversationId + timestamp, jos tilaat molemmat.

{
  "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 - lomakkeen pysyvä tekninen nimi, joka ei muutu lomaketta uudelleennimettäessä; käytä sitä lähetysten ohjaamiseen omassa järjestelmässäsi. formName on vierailijoille näytettävä nimi.
  • fields käyttää samoja {name, value, type} -tietueita kuin lead.created: monivalintojen arvot ovat taulukoita ja tiedostojen arvot latauslinkkejä.
  • purpose - STANDALONE, LEAD_COLLECTION tai HUMAN_CONTACT riippuen siitä, miten lomake on liitetty chatbotiin.

conversation.started

Laukeaa, kun vierailija lähettää uuden keskustelun ensimmäisen viestin.

{
  "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 - vierailijan aloitusviestin tarkka teksti. null, jos keskustelu avattiin ilman viestisisältöä.
  • chatSource - kanava, josta keskustelu saapui: WIDGET, WHATSAPP, MESSENGER, VOICE, VOICE_PHONE, API, BOOKING, AIRBNB tai IDOBOOKING.
  • byAdmin - true, kun keskustelu tulee ChatLabin hallintapaneelin chatbot-esikatselusta eikä oikealta vierailijalta. Käytä tätä pitääksesi omat testikeskustelusi poissa CRM:stäsi.
  • countryCode - vierailijan IP-osoitteesta määritetty ISO-maakoodi, null jos sitä ei pystytty määrittämään.
  • ipAddress - vierailijan IP-osoite ChatLabin havaitsemana, null jos se ei ole saatavilla. Käsittele sitä henkilötietona GDPR:n mukaisesti ja tallenna se vain, jos sinulla on siihen laillinen peruste.

conversation.rated

Laukeaa, kun vierailija arvioi botin vastauksen peukalolla ylös tai alas (katso Keskustelun arviointi).

{
  "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 tai NEGATIVE. Arvioinnin poistaminen ei laukaise tapahtumaa, joten et koskaan saa neutraalia arvoa.

conversation.summarized

Laukeaa, kun ChatLab luo yhteenvedon päättyneestä keskustelusta.

{
  "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"
  }
}
  • summary - luotu yhteenvetoteksti. Yhteenvedot luodaan muutaman minuutin kuluttua siitä, kun keskustelu on hiljentynyt, joten tämä tapahtuma saapuu myöhemmin kuin muut keskustelutapahtumat.
  • language - sen kielen ISO-koodi, jolla yhteenveto kirjoitettiin (keskustelun kielen mukaisesti).

client.summarized

Laukeaa, kun ChatLab päivittää asiakkaan tekoälyprofiilin. Profiili muodostetaan uudelleen aiemmasta profiilista ja juuri päättyneen keskustelun yhteenvedosta, joten tämä tapahtuma seuraa saman keskustelun conversation.summarized-tapahtumaa. Asiakkaat tunnistetaan sähköpostiosoitteen perusteella, minkä vuoksi osoite toistetaan data-objektin päätasolla.

{
  "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 - tunniste, jolla asiakas voidaan yhdistää omaan CRM-järjestelmääsi. Se on null anonyymeille vierailijoille, jotka eivät koskaan jättäneet osoitetta, ja tapahtuma laukeaa heillekin - ohita nämä toimitukset, jos integraatiosi perustuu sähköpostiin.
  • client - yhteystietue, joka ChatLabilla on tästä henkilöstä: email, name, phone, countryCode ja ipAddress. Jokainen avain on aina läsnä; tuntemattomat arvot ovat null.
  • clientSummary - koko profiiliteksti pelkkänä tekstinä, ei erotustietona (diff). Se korvaa aiemman yhteenvedon kokonaan, joten tallenna se korvaamalla vanha teksti sen sijaan, että lisäisit sen perään.
  • Profiili luodaan uudelleen vain boteille, joissa chat memory (keskustelumuisti) on käytössä, ja vain keskusteluille, jotka olivat toimettomina tarpeeksi kauan yhteenvedon tekemiseksi - odota tämän tapahtuman saapuvan minuuttien kuluttua keskustelun päättymisestä, ei välittömästi.

live_chat.requested

Laukeaa, kun tekoäly siirtää keskustelun Live Chatiin, joko siksi, että vierailija pyysi ihmistä, tai siksi, että botti päätti asiakaspalvelijan olevan tarpeen.

{
  "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 - toistaiseksi aina AI, koska siirron tekee aina botin live chat -toiminto, silloinkin kun vierailija pyytää sitä suoraan sanoin. Käsittele sitä avoimena enumina: varaudu tuntemattomiin arvoihin sen sijaan, että olettaisit arvon olevan aina pelkkä AI.
  • Tapahtuma ilmoittaa vain, että siirtoa pyydettiin, ei sitä, että asiakaspalvelija olisi ottanut sen vastaan. Odota sitä varten tapahtumaa live_chat.started.

live_chat.started

Laukeaa, kun asiakaspalvelija liittyy mukaan ja live chat -istunto todella alkaa.

{
  "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 on tarkoituksella tyhjä. Kaikki tarvitsemasi tiedot ovat perusrakenteessa (envelope): botId tunnistaa chatbotin ja conversationId / sessionId yhdistävät tapahtuman keskusteluun, josta sait jo aiemmin tapahtuman live_chat.requested.

live_chat.ended

Laukeaa, kun live chat -istunto päättyy.

{
  "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 - kuinka kauan asiakaspalvelija oli keskustelussa laskettuna istunnon alkamishetkestä. Kenttä jätetään pois niissä harvoissa tapauksissa, joissa istunto päättyy ilman, että sitä olisi koskaan aloitettu.

ai_action.executed

Laukeaa joka kerta, kun botti suorittaa tekoälytoiminnon (AI action) - hallitun integraatiokutsun tai mukautetun API-funktion. Tämä on erittäin suuren volyymin tapahtuma: aktiivinen verkkokauppabotti voi suorittaa satoja toimintoja päivässä, ja yksi ainoa vierailijan viesti voi laukaista useita toimintoja. Tilaa se erilliseen päätepisteeseen tai varmista, että vastaanottimesi kestää liikennemäärän.

{
  "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 - suoritetun toiminnon nimi tekoälyn näkemänä, esimerkiksi search_products hallitulle integraatiolle tai nimi, jonka annoit mukautetulle API-toiminnolle.
  • status - SUCCESS tai ERROR.
  • durationMs - kuinka kauan toiminnon suoritus kesti millisekunteina. Hyödyllinen hitaan integraation havaitsemiseen ennen kuin vierailijat valittavat siitä.
  • errorMessage - epäonnistumisen syy, täytetään vain silloin, kun status on ERROR; muulloin null.

webhook.test

Lähetetään Send sample event (Lähetä esimerkkitapahtuma) -painikkeesta, kun suoritat tavallisen yhteyden testauksen. Allekirjoitettu täsmälleen kuten oikea tapahtuma.

{
  "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 - kiinteä teksti, aina sama. Perusrakenteen kentät sisältävät esimerkkilukuja, joten älä koskaan käsittele webhook.test-toimitusta oikeana datana.
  • Tämä on ainoa tapahtumatyyppi, jota et voi tilata päätepisteeseen: se lähetetään tarpeen mukaan hallintapaneelista ja saapuu aina siihen päätepisteeseen, jota napsautit, riippumatta siitä, mitä tapahtumia se kuuntelee.

Tietoturva: toimitusten todentaminen

Jokainen toimitus sisältää neljä otsaketta (headers):

Otsake Arvo
X-ChatLab-Signature sha256=<hex hmac> - hyötykuorman HMAC-SHA256-allekirjoitus
X-ChatLab-Timestamp Unix-aikaleima sekunteina, jolloin toimitus allekirjoitettiin
X-ChatLab-Event Tapahtumatyyppi, esim. lead.created
X-ChatLab-Delivery Yksilöllinen toimitustunnus, joka vastaa tekstiosan eventId-kenttää

Allekirjoitus lasketaan HMAC-SHA256-tiivisteenä merkkijonosta {timestamp}.{rawBody} päätepisteen salaisuudella (endpoint secret), missä {timestamp} on X-ChatLab-Timestamp-otsakkeen arvo ja {rawBody} on pyynnön raaka, jäsentämätön runko. Vahvista allekirjoitus aina raakojen tavujen perusteella - jäsennetyn JSON-datan uudelleenserialisointi muuttaa tavujärjestystä ja rikkoo allekirjoituksen.

Suojaudu toistohyökkäyksiltä (replay attacks) hylkäämällä toimitukset, joiden X-ChatLab-Timestamp on vanhempi kuin 5 minuuttia.

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 (!signature || !timestamp) 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 + '.' + 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 === '') {
        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);
}

Jos vahvistus epäonnistuu, vastaa tilakoodilla 401 ja hylkää hyötykuorma. Älä koskaan käsittele vahvistamattomia toimituksia - kuka tahansa osoitteesi löytävä voi lähettää siihen mielivaltaista JSON-dataa POST-pyynnöllä.

Toimitusten toimintaperiaate

Huomioi nämä toimintatakuut ennen webhookien käyttöönottoa:

  • Vastaa nopeasti. Päätepisteesi on vastattava 3 sekunnin kuluessa, tai toimitus katsotaan epäonnistuneeksi. Palauta 2xx-vastaus heti ja käsittele hyötykuorma asynkronisesti (aseta se jonoon ja kuittaa vasta sitten) - älä tee CRM-kutsuja tai tietokantakirjauksia ennen vastaamista.
  • Kertaluonteinen lähetys (fire-and-forget, at-most-once). Jokaiselle tapahtumalle tehdään tasan yksi toimitusyritys - uudelleenyrityksiä ei ole. Jos päätepisteesi on alhaalla, pyyntö aikakatkaistaan tai se palauttaa muun kuin 2xx-tilakoodin, kyseinen tapahtuma menetetään eikä sitä toimiteta uudelleen. Webhookit ovat ilmoituksia, eivät replikoitu tietovarasto: kun tarvitset taatun kattavuuden, vertaa tietoja Management API -rajapinnan tai liidivientien kanssa.
  • Vikavirtakytkin (circuit breaker). Jos botille tapahtuu 5 peräkkäistä epäonnistunutta toimitusta, kyseisen botin toimitukset keskeytetään 5 minuutiksi. Tauon aikana tapahtuvat tapahtumat hylätään, ja toimituslokiin tulee CIRCUIT_OPEN-merkintöjä, jotta näet tarkalleen, milloin ja miksi liikenne estettiin. Avoimen kytkimen ohittamia toimituksia ei lasketa mukaan automaattiseen käytöstäpoistoon.
  • Automaattinen käytöstäpoisto. Tarkistus suoritetaan vain toimituksen epäonnistuessa, ei koskaan ajastetusti. Jos toimitus epäonnistuu eikä 7 päivään ole ollut yhtään onnistunutta toimitusta - laskettuna viimeisimmästä onnistumisesta tai päätepisteen luontipäivästä, jos se ei ole koskaan onnistunut - päätepiste kytketään pois päältä ja saat sähköposti-ilmoituksen. Yksittäinen 2xx-vastaus milloin tahansa nollaa tämän laskurin. Päätepistettä, joka ei vastaanota liikennettä, ei koskaan poisteta käytöstä, koska mikään ei epäonnistu. Ota se uudelleen käyttöön Account settings (tilin asetukset) -osiosta, kun vastaanottimesi on korjattu; epäonnistumislaskuri ja automaattisen käytöstäpoiston aikaleima nollataan, kun kytket sen takaisin päälle, eikä poissaolon aikana menetettyjä tapahtumia toimiteta jälkikäteen.
  • 410 Gone. Jos päätepisteesi vastaa HTTP-tilakoodilla 410 Gone, ChatLab poistaa sen käytöstä välittömästi. Käytä tätä päätepisteen ohjelmalliseen poistamiseen vastaanottavalta puolelta.
  • Idempotenssi. Kaksinkertaisia toimituksia ei pitäisi tapahtua normaalissa toiminnassa, mutta jos käsittelysi on oltava täysin idempotenttia, poista kaksoiskappaleet eventId-kentän perusteella (saatavilla myös X-ChatLab-Delivery-otsakkeessa).

Rajoitukset

  • Enintään 10 webhook-päätepistettä tiliä kohden.
  • Toimituslokien säilytysaika: 14 päivää. Vanhemmat merkinnät poistetaan automaattisesti.

Aiheeseen liittyvät artikkelit