Hilfezentrum
Chat API

Webhooks

Zuletzt aktualisiert:

Webhooks - Übersicht

Über Webhooks benachrichtigt ChatLab Ihre Systeme in dem Moment, in dem in Ihren Chatbots etwas passiert. Statt die Management API per Polling abzufragen oder Daten manuell zu exportieren, registrieren Sie einen HTTPS-Endpunkt, an den ChatLab in Echtzeit einen signierten HTTP-POST sendet - wenn ein Besucher einen Lead hinterlässt, ein Kontaktformular absendet, eine Konversation bewertet, einen menschlichen Agenten anfordert oder wenn eine KI-Aktion ausgeführt wird.

Typische Anwendungsfälle:

  • neue Leads sofort nach der Erfassung direkt in Ihr CRM übertragen
  • Ihr Team in Slack benachrichtigen, sobald ein Besucher einen Live-Chat anfordert
  • Konversationsbewertungen und Zusammenfassungen in Ihre eigenen Analysetools einspeisen
  • die Ausführung von KI-Aktionen überwachen und bei Fehlern warnen

Verfügbarkeit: Ihr Konto muss die Funktion Webhooks enthalten.

Konfiguration: Öffnen Sie in der Admin-App Kontoeinstellungen -> Webhooks (Account settings -> Webhooks; direkt neben dem Bereich Management API). Webhooks gelten auf Kontoebene - ein einzelner Endpunkt kann Ereignisse von allen Ihren Bots oder von einem ausgewählten Bot empfangen.

Einen Endpunkt einrichten

  1. Öffnen Sie Account settings -> Webhooks (Kontoeinstellungen -> Webhooks) und klicken Sie auf Add endpoint (Endpunkt hinzufügen).
  2. Füllen Sie das Endpunkt-Formular aus:
    • Name - eine Bezeichnung für Ihre eigene Übersicht, z. B. "CRM sync" oder "Slack alerts".
    • URL - die HTTPS-Adresse, an die ChatLab Ereignisse per POST sendet.
    • Events (Ereignisse) - wählen Sie aus, welche Ereignistypen dieser Endpunkt empfängt (siehe Katalog unten). Wählen Sie nur das aus, was Sie benötigen; Ereignisse mit hohem Volumen wie ai_action.executed können viel Datenverkehr erzeugen.
    • Bot filter (Bot-Filter) (optional) - wählen Sie einen Bot oder All bots (Alle Bots) für alle Bots in Ihrem Konto aus.
    • Custom form filter (Filter für benutzerdefinierte Formulare) (optional) - leitet Übermittlungen eines bestimmten benutzerdefinierten Formulars an diesen Endpunkt weiter. Dies schränkt nur das Ereignis custom_form.submitted ein; jedes andere Ereignis, das Sie abonnieren (Leads, Kontaktanfragen, Konversationen, Live Chat, KI-Aktionen), wird unabhängig von dieser Einstellung zugestellt.
  3. Absenden. Das Endpunkt-Secret wird genau einmal im Erfolgsdialog angezeigt - kopieren Sie es jetzt und speichern Sie es sicher ab. Sie benötigen es zur Verifizierung von Signaturen (siehe Abschnitt Sicherheit unten). Der Klartext kann später nicht mehr abgerufen werden.

Jeder Endpunkt verfügt außerdem über:

  • Schalter zum Aktivieren/Deaktivieren - pausiert Zustellungen, ohne den Endpunkt zu löschen. Deaktivierte Endpunkte verwerfen Ereignisse stillschweigend (sie werden nicht für später in die Warteschlange gestellt).
  • Send sample event (Beispielereignis senden) - stellt eine signierte Testanfrage an Ihre URL zu, sodass Sie Ihren Empfänger durchgängig überprüfen können. Sie können den Ereignistyp auswählen und die Beispielwerte vor dem Senden anpassen, damit Ihr Handler realistische Daten sieht. Der Test trifft als reguläre Zustellung ein, wobei eventType Ihrer Auswahl entspricht (oder als webhook.test für eine einfache Verbindungskontrolle).
  • Roll secret (Secret rotieren) - generiert ein neues Secret und macht das alte ungültig. Verwenden Sie dies, falls das Secret offengelegt worden sein könnte. Das neue Secret wird ebenfalls nur einmal angezeigt. Für eine kontrollierte Rotation pausieren Sie den Endpunkt, rotieren und kopieren das neue Secret, aktualisieren den Empfänger, aktivieren den Endpunkt wieder und senden ein Beispielereignis. Ereignisse während der Pause werden nicht in die Warteschlange gestellt. Planen Sie diese Unterbrechung vor der Rotation ein.
  • Delivery log (Zustellprotokoll) - eine Liste der letzten Zustellungen pro Endpunkt mit Zeitstempel, Ereignistyp, dem von Ihrem Server zurückgegebenen HTTP-Status und der Antwortzeit. Fehlgeschlagene Zustellungen und Circuit-Breaker-Pausen sind hier sichtbar. Das Protokoll wird 14 Tage lang aufbewahrt.

Event-Envelope

Jede Zustellung ist ein HTTP-POST mit Content-Type: application/json. Der Body enthält immer denselben Envelope; das data-Objekt ist spezifisch für den jeweiligen Event-Typ:

{
  "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 - eindeutig pro Event. Nutzen Sie diesen Wert zur Deduplizierung, falls Ihre Verarbeitung idempotent sein muss.
  • eventType - einer der unten dokumentierten Typen; wird auch im X-ChatLab-Event-Header übermittelt.
  • timestamp - ISO 8601 UTC-Zeitpunkt, an dem der Zustellungs-Envelope erstellt wurde.
  • botId / botName - der Bot, zu dem das Event gehört.
  • conversationId / sessionId - der Kontext der Unterhaltung, sofern zutreffend.

Ereigniskatalog

lead.created

Wird ausgelöst, wenn ein Besucher seine Kontaktdaten übermittelt - über das Lead-Erfassungsformular, das Vorab-Formular für den Live-Chat oder ein benutzerdefiniertes Formular zur Lead-Erfassung.

{
  "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 - wie die Kontaktdaten erfasst wurden: LEAD_COLLECTION_FORM (Lead-Erfassungsformular), LIVE_CHAT_FORM (Vorab-Formular für den Live-Chat), CONVERSATION (die KI hat die Daten während des Chats erfasst), ADMIN_DATA_UPDATE oder UPDATE_CLIENT_CONTEXT (auf Seiten von ChatLab bearbeitet). Das Absenden des Support-Formulars für menschliche Hilfe löst dieses Ereignis niemals aus - stattdessen wird contact_form.submitted ausgelöst.
  • email, name, phone - die auf den Lead-Datensatz abgebildeten Kontaktdaten.
  • Wird ein benutzerdefiniertes Formular zur Lead-Erfassung verwendet, ist jedes in diesem Formular definierte Feld in fields enthalten, in der Reihenfolge des Formulars, und formCodeName / formName identifizieren das Formular. Beim klassischen Lead-Formular sind beide null und fields ist ein leeres Array.
  • Jeder Eintrag in fields ist {name, value, type}. name ist der technische Name des Feldes, der auch bei Änderungen der Beschriftung stabil bleibt - nutzen Sie ihn für das Mapping in Ihr CRM.
  • Bei multichoice-Feldern ist value ein Array der ausgewählten Optionen. Checkbox-Felder sind einzelne Einträge mit den Werten "true" / "false".
  • Bei file-Feldern ist value ein Download-Link zur hochgeladenen Datei; der Webhook überträgt niemals Dateiinhalte direkt.
  • pageUrl - die Seite, auf der sich der Besucher beim Absenden befand.

contact_form.submitted

Wird ausgelöst, wenn ein Besucher das Kontaktformular für menschlichen Support oder ein dafür genutztes benutzerdefiniertes Formular absendet.

{
  "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 - die vom Besucher hinterlassene Adresse, an die Ihr Support-Team antworten sollte.
  • source - CUSTOM_FORM, wenn ein benutzerdefiniertes Formular hinter dem Kontaktformular steht, CONTACT_FORM für das integrierte Formular.
  • formCodeName / formName - identifizieren das benutzerdefinierte Formular hinter der Anfrage; beide sind null beim integrierten Formular.
  • Wird ein benutzerdefiniertes Formular verwendet, ist jedes definierte Feld dieses Formulars in fields enthalten (gleiches {name, value, type}-Format wie bei lead.created). Beim integrierten Kontaktformular sind nur email und message befüllt, fields ist ein leeres Array und die Formular-Identifikatoren lauten null.
  • message - das zugeordnete Nachrichtenfeld oder alle ausgefüllten Werte zusammengefügt, falls das Formular kein spezifisches Nachrichtenfeld definiert.

custom_form.submitted

Wird für jedes Absenden eines benutzerdefinierten Formulars ausgelöst, unabhängig von dessen Zweck. Beachten Sie, dass Formulare, die der Lead-Erfassung oder dem menschlichen Kontakt dienen, auch ihr jeweiliges Ereignis lead.created / contact_form.submitted auslösen - abonnieren Sie je nach Bedarf entweder die allgemeine oder die spezialisierte Ansicht, um doppelte Geschäftsaktionen zu vermeiden. Die beiden Ereignisfamilien besitzen unterschiedliche Ereignis-IDs; conversationId zusammen mit timestamp ist keine verlässliche Übermittlungskennung.

{
  "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 - der stabile Systemname des Formulars, der sich bei Umbenennungen nicht ändert; nutzen Sie ihn für das Routing in Ihrem eigenen System. formName ist die sichtbare Beschriftung für Besucher.
  • fields verwendet dieselben {name, value, type}-Einträge wie lead.created: Mehrfachauswahl-Werte sind Arrays, Dateiwerte sind Download-Links.
  • purpose - STANDALONE, LEAD_COLLECTION oder HUMAN_CONTACT, je nachdem, wie das Formular an den Chatbot angebunden ist.

conversation.started

Wird ausgelöst, wenn ein Besucher die erste Nachricht einer neuen Konversation sendet.

{
  "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 - der genaue Wortlaut der ersten Nachricht des Besuchers. null, falls die Konversation ohne Nachrichteninhalt eröffnet wurde.
  • chatSource - der Kanal, über den die Konversation eingegangen ist: WIDGET, WHATSAPP, MESSENGER, VOICE, VOICE_PHONE, API, BOOKING, AIRBNB oder IDOBOOKING.
  • byAdmin - true, wenn die Konversation aus der Chatbot-Vorschau im ChatLab-Adminbereich stammt und nicht von einem echten Besucher. Nutzen Sie dies, um Ihre eigenen Test-Chats aus Ihrem CRM herauszuhalten.
  • countryCode - ISO-Ländercode, ermittelt anhand der IP-Adresse des Besuchers; null, falls er nicht bestimmt werden konnte.
  • ipAddress - die IP-Adresse des Besuchers aus Sicht von ChatLab; null, falls nicht verfügbar. Behandeln Sie diese nach DSGVO als personenbezogene Daten und speichern Sie sie nur bei Vorliegen einer entsprechenden Rechtsgrundlage.

conversation.rated

Wird ausgelöst, wenn ein Besucher eine positive oder negative Bewertung für eine Konversation abgibt (siehe Konversationsbewertung).

{
  "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 oder NEGATIVE. Das Zurücksetzen einer Bewertung löst kein Ereignis aus; Sie erhalten daher nie einen neutralen Wert.

conversation.summarized

Wird ausgelöst, wenn ChatLab eine Zusammenfassung einer beendeten Konversation erstellt.

{
  "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 - der generierte Text der Zusammenfassung. Die Erstellung erfolgt asynchron und hängt von der Zusammenfassungskonfiguration sowie dem Verarbeitungsplan des Bots ab; gehen Sie nicht von einer festen Zustellzeit aus.
  • language - das interne Locale des Bots, das an die Zusammenfassung übergeben wurde, zum Beispiel en_US; gehen Sie nicht davon aus, dass dies der Konversationssprache des Besuchers entspricht.

client.summarized

Wird ausgelöst, wenn ChatLab das KI-Profil eines Kunden aktualisiert. Das Profil wird aus dem vorherigen Profil und der Zusammenfassung der soeben beendeten Konversation neu aufgebaut und kann nach conversation.summarized generiert werden. Die Reihenfolge der Zustellung ist nicht garantiert. Die Payload enthält eine E-Mail-Adresse, sofern bekannt; ChatLab kann Kunden jedoch auch ohne E-Mail-Adresse führen.

{
  "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 - die Kennung zum Abgleich des Kunden mit Ihrem eigenen CRM. Bei anonymen Besuchern, die keine Adresse hinterlassen haben, lautet der Wert null, und das Ereignis wird dennoch ausgelöst - überspringen Sie diese Zustellungen, falls Ihre Integration auf E-Mail-Adressen basiert.
  • client - der Kontaktdatensatz, den ChatLab für diese Person führt: email, name, phone, countryCode und ipAddress. Jeder Schlüssel ist stets vorhanden; unbekannte Werte lauten null.
  • clientSummary - der vollständige Profiltext als Klartext, kein Diff. Er ersetzt die bisherige Zusammenfassung vollständig; speichern Sie ihn daher überschreibend statt anhängend.
  • Das Profil wird nur für Bots mit aktivem Chat-Gedächtnis neu aufgebaut und nur bei Konversationen, die lange genug inaktiv waren, um zusammengefasst zu werden - rechnen Sie mit diesem Ereignis einige Minuten nach Ende der Konversation, nicht sofort.

live_chat.requested

Wird ausgelöst, wenn die KI die Konversation an den Live Chat (Live-Chat) übergibt - entweder weil der Besucher nach einem Mitarbeiter gefragt hat oder weil der Bot entschieden hat, dass menschliche Hilfe nötig ist.

{
  "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 - derzeit immer AI, da die Übergabe stets durch die Live-Chat-Aktion des Bots ausgelöst wird, auch wenn der Besucher direkt darum bittet. Behandeln Sie dies als offenes Enum: Fangen Sie unbekannte Werte ab, statt fest auf AI zu prüfen.
  • Dieses Ereignis deckt die KI-Aktion zur Übergabeanfrage ab, nicht alle Wege, wie ein Besucher den Live-Chat öffnen kann. Es belegt nicht, dass ein Mitarbeiter beigetreten ist.

live_chat.started

Wird ausgelöst, wenn die Live-Chat-Sitzung nach dem Absenden des Übergabeformulars durch den Besucher erstellt wird. Zu diesem Zeitpunkt muss noch kein Mitarbeiter beigetreten sein oder geantwortet haben; werten Sie dies nicht als Nachweis für eine menschliche Interaktion.

{
  "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 ist bewusst leer. Alle benötigten Daten befinden sich im Envelope: botId identifiziert den Chatbot und conversationId / sessionId verknüpfen das Ereignis mit der Konversation. Sie können es auch ohne ein vorangegangenes live_chat.requested erhalten, beispielsweise wenn der Besucher das Live-Chat-Bedienelement im Widget genutzt hat.

live_chat.ended

Wird ausgelöst, wenn die Live-Chat-Sitzung endet.

{
  "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 - abgelaufene Dauer der Live-Chat-Sitzung, gezählt ab Sitzungserstellung inklusive Wartezeit auf einen Mitarbeiter. Das Feld entfällt in dem seltenen Fall, dass eine Sitzung beendet wird, ohne jemals gestartet worden zu sein.

ai_action.executed

Wird jedes Mal ausgelöst, wenn der Bot eine KI-Aktion ausführt - einen verwalteten Integrationsaufruf oder eine benutzerdefinierte API-Funktion. Dies ist ein Ereignis mit hohem Volumen: Ein aktiver E-Commerce-Bot kann Hunderte von Aktionen pro Tag ausführen, und eine einzige Besucherinteraktion kann mehrere davon anstoßen. Abonnieren Sie es über einen dedizierten Endpunkt oder stellen Sie sicher, dass Ihr System dieses Datenvolumen verarbeiten kann.

{
  "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 - der Name der ausgeführten Aktion aus Sicht der KI, beispielsweise search_products für eine verwaltete Integration oder der von Ihnen vergebene Name einer benutzerdefinierten API-Aktion.
  • status - SUCCESS oder ERROR.
  • durationMs - Dauer der Aktionsausführung in Millisekunden. Nützlich, um langsame Integrationen zu erkennen, bevor sich Besucher darüber beschweren.
  • errorMessage - der Grund für den Fehler; nur befüllt, wenn status den Wert ERROR hat, andernfalls null.

webhook.test

Wird über die Schaltfläche Send sample event (Beispiel-Event senden) gesendet, wenn Sie eine einfache Verbindungskontrolle durchführen. Signiert exakt wie ein echtes Ereignis.

{
  "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 - fester Text, bleibt immer gleich. Die Envelope-Felder enthalten Beispielwerte; behandeln Sie eine Zustellung von webhook.test daher niemals als echte Daten.
  • Dies ist der einzige Ereignistyp, den Sie nicht fest an einem Endpunkt abonnieren können: Er wird bei Bedarf direkt aus dem Adminbereich gesendet und erreicht stets den Endpunkt, den Sie angeklickt haben - unabhängig davon, auf welche Ereignisse dieser lauscht.

Sicherheit: Zustellungen verifizieren

Jede Zustellung enthält vier Header:

Header Wert
X-ChatLab-Signature sha256=<hex hmac> - HMAC-SHA256-Signatur der Payload
X-ChatLab-Timestamp Unix-Zeitstempel in Sekunden, an dem die Zustellung signiert wurde
X-ChatLab-Event Der Ereignistyp, z. B. lead.created
X-ChatLab-Delivery Eindeutige Zustellungs-ID, identisch mit eventId im Body

Die Signatur wird als HMAC-SHA256 über die Zeichenkette {timestamp}.{rawBody} unter Verwendung Ihres Endpunkt-Geheimnisses (Endpoint Secret) berechnet, wobei {timestamp} der Wert von X-ChatLab-Timestamp und {rawBody} der unverarbeitete, ungeparste Request-Body ist. Führen Sie die Überprüfung immer anhand der rohen Bytes durch - das erneute Serialisieren von geparstem JSON ändert die Bytefolge und macht die Signatur ungültig.

Um sich vor Replay-Angriffen zu schützen, weisen Sie Zustellungen ab, deren X-ChatLab-Timestamp älter als 5 Minuten ist.

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);
}

Wenn die Überprüfung fehlschlägt, antworten Sie mit 401 und verwerfen Sie die Payload. Verarbeiten Sie niemals nicht verifizierte Zustellungen - jeder, der Ihre URL herausfindet, kann beliebigen JSON-Code per POST daran senden.

Zustellungsverhalten

Machen Sie sich mit diesen Zustellungsregeln vertraut, bevor Sie Webhooks implementieren:

  • Schnell antworten. Ihr Endpunkt muss innerhalb von 3 Sekunden antworten, andernfalls gilt die Zustellung als fehlgeschlagen. Verifizieren Sie die Signatur, reihen Sie das Event dauerhaft in eine Warteschlange ein und bestätigen Sie den Empfang innerhalb dieses Zeitfensters mit 2xx. Führen Sie langsamere CRM-Aufrufe und Geschäftslogik asynchron aus.
  • Fire-and-Forget, höchstens einmal (at-most-once). Passende, aktivierte Endpunkte erhalten höchstens einen Zustellversuch - es gibt keine erneuten Versuche (Retries). Pausierte Endpunkte und ausgelöste Schutzschalter (Circuit Breaker) können selbst diesen Versuch unterdrücken. Wenn Ihr Endpunkt offline ist, ein Timeout auftritt oder ein Status ungleich 2xx zurückgegeben wird, ist dieses Event verloren und wird nicht erneut zugestellt. Webhooks sind Benachrichtigungen, kein replizierter Datenspeicher: Nutzen Sie die Bot Talk API Konversations-Endpunkte oder Lead-Exporte, um gespeicherte Daten abzugleichen, sofern dies unterstützt wird. Die Management API deckt Bot-Einstellungen und die Nutzung ab, stellt jedoch kein vollständiges Event-Archiv dar. Manche Events lassen sich über diese Schnittstellen nicht rekonstruieren.
  • Circuit Breaker (Schutzschalter). Nach 5 aufeinanderfolgenden fehlgeschlagenen Zustellungen für ein Endpunkt/Bot-Paar werden die Zustellungen für dieses Paar für 5 Minuten pausiert. Events, die während der Pause auftreten, werden verworfen, und das Zustellprotokoll weist CIRCUIT_OPEN-Einträge aus, um unterdrückte Versuche zu kennzeichnen. Zustellungen, die durch einen offenen Circuit Breaker übersprungen werden, zählen nicht für die automatische Deaktivierung.
  • Automatische Deaktivierung. Die Prüfung erfolgt genau in dem Moment, in dem eine Zustellung fehlschlägt, niemals über einen Timer. Wenn eine Zustellung fehlschlägt und es 7 Tage lang keine erfolgreiche Zustellung gab - gerechnet ab dem letzten Erfolg oder ab dem Erstellungsdatum des Endpunkts, falls es noch nie einen Erfolg gab -, wird der Endpunkt deaktiviert und Sie erhalten eine E-Mail-Benachrichtigung. Ein einziges 2xx zu einem beliebigen Zeitpunkt setzt diesen Zähler zurück. Ein Endpunkt, der keinen Datenverkehr empfängt, wird niemals deaktiviert, da auch nichts fehlschlägt. Aktivieren Sie ihn nach der Fehlerbehebung Ihres Empfängers in den Kontoeinstellungen wieder; der Fehlerzähler und der Zeitstempel für die automatische Deaktivierung werden beim erneuten Einschalten zurückgesetzt, und Events, die während der Deaktivierung verpasst wurden, werden nicht nachgeliefert.
  • 410 Gone. Wenn Ihr Endpunkt mit HTTP 410 Gone antwortet, deaktiviert ChatLab ihn sofort. Nutzen Sie dies, um einen Endpunkt von der Empfängerseite aus programmatisch stillzulegen.
  • Idempotenz. Doppelte Zustellungen sind im Normalbetrieb nicht zu erwarten. Wenn Ihre Verarbeitung jedoch strikt idempotent sein muss, deduplizieren Sie anhand von eventId (auch im Header X-ChatLab-Delivery verfügbar).

Die Reihenfolge der Zustellung ist nicht garantiert. Speichern Sie die Event-ID und gestalten Sie die geschäftliche Verarbeitung idempotent. Implementieren Sie eigene Aufbewahrungs- und Zugriffskontrollen für Webhook-Nutzdaten, da diese personenbezogene Daten und Download-Links zu Dateien enthalten können.

Die Signaturbeispiele verwenden Node.js crypto und PHP hash_equals. Erfassen Sie den unveränderten Request-Body (Raw Body), bevor Sie ihn parsen.

Limits

  • Bis zu 10 Webhook-Endpunkte pro Konto.
  • Speicherdauer des Zustellungsprotokolls: 14 Tage. Ältere Einträge werden automatisch entfernt.

Verwandte Artikel