# Leads und Übergaben per Webhook an deine Tools senden

> Sende neue Leads, Übergaben und Live-Chat-Anfragen, Formulareinsendungen, gebuchte Termine und Rücksendeanfragen aus intoCHAT als signiertes JSON an deinen Server, Zapier, Make oder n8n: Events, Inhalte, Signaturen und Wiederholungen.

Ein Webhook schickt ein Ereignis deines Agenten innerhalb von Sekunden an eine Adresse deiner Wahl, als signierte JSON-Anfrage per `POST`. So landen neue Leads in deinem CRM, oder eine Automatisierung startet. Das funktioniert mit deinem eigenen Server und mit Automatisierungstools, die eingehende Webhooks annehmen, etwa Zapier, Make oder n8n: Du nutzt den Auslöser des Tools für eingehende Webhooks. Die eigenen intoCHAT-Apps für Zapier und Make sind fertig und werden verfügbar, sobald sie in den Verzeichnissen dieser Tools veröffentlicht sind; siehe [Zapier und Make](/de/docs/zapier-and-make). Willst du diese Ereignisse als Nachrichten in einem Slack-Channel, brauchst du keinen Webhook: Nutze die [Slack-Benachrichtigungen](/de/docs/lead-alerts-and-export).

## Events

| Event | Name im Dashboard | Wird gesendet, wenn |
| --- | --- | --- |
| `lead.created` | **New lead** | ein neuer Kontakt gespeichert wird, aus dem [Lead-Formular](/de/docs/lead-collection) oder aus Kontaktdaten, die der Besucher in den Chat geschrieben hat. |
| `handoff.requested` | **Hand-off requested** | der Agent ein Gespräch per E-Mail an dein Team geschickt hat. Siehe [Übergabe per E-Mail](/de/docs/handoff). |
| `form.submitted` | **Form submitted** | ein Besucher eines deiner [Chat-Formulare](/de/docs/chat-forms) abgeschickt hat. |
| `booking.created` | **Meeting booked** | ein Besucher im Chat einen Termin gebucht hat. Siehe [Terminbuchung mit Cal.com oder Calendly](/de/docs/booking). |
| `live_chat.requested` | **Live chat requested** | der Agent dein Team gebeten hat, in den Chat zu kommen, weil ein Besucher nach einer Person gefragt hat, während jemand aus deinem Team online war. Siehe [Live-Chat](/de/docs/live-chat). |
| `return.requested` | **Return requested** | ein Besucher darum gebeten hat, Artikel einer Bestellung zurückzusenden oder umzutauschen, die der Agent verifiziert hat. Siehe [Bestellstatus und Rücksendungen](/de/docs/orders). |

`lead.created` kommt einmal pro Kontakt. Sendet ein wiederkehrender Besucher dieselben Daten noch einmal, gibt es kein neues Event, ebenso wenig, wenn ein Chat einem dort schon gespeicherten Kontakt eine Telefonnummer hinzufügt.

`handoff.requested` kommt nur, wenn die E-Mail an dein Team tatsächlich verschickt wurde. Übergaben aus dem **Playground** lösen es ebenfalls aus, gekennzeichnet mit `"test": true`. Hast du [Helpdesk-Tickets](/de/docs/helpdesk-tickets) verbunden, kommt es, sobald das Ticket erstellt wurde oder das Erstellen fehlgeschlagen ist, meist ein paar Sekunden später, und enthält das Ticket, wenn es eines gibt.

`form.submitted` kommt einmal pro Einsendung. Ein Formular lässt sich pro Gespräch nur einmal abschicken, und eine Einsendung löst nie `lead.created` aus, auch wenn das Formular nach einer E-Mail-Adresse fragt.

`booking.created` kommt einmal pro Buchung, nachdem Cal.com oder Calendly sie angenommen und intoCHAT sie gespeichert hat. Eine Buchung löst nie `lead.created` aus. Buchungen aus dem **Playground** sind echte Buchungen und lösen es ebenfalls aus, ohne Test-Kennzeichen. Spätere Änderungen in Cal.com oder Calendly, etwa eine Absage, lösen kein Event aus.

`live_chat.requested` kommt einmal pro Anfrage, wenn der Agent dein Team bittet dazuzukommen, egal ob jemand dazukommt oder nicht. Fragt ein Besucher erneut, nachdem die Anfrage verfallen oder der Live-Chat beendet ist, kommt ein neues Event. Der **Playground** fordert nie einen Live-Chat an und löst dieses Event daher nie aus.

`return.requested` kommt einmal pro Rücksendeanfrage, sobald intoCHAT sie gespeichert hat. Jedes Gespräch kann eine Anfrage pro Bestellung senden, eine erneute Anfrage für dieselbe Bestellung löst also nichts aus. Anfragen aus dem **Playground** lösen es ebenfalls aus, ohne Test-Kennzeichen.

## Webhook hinzufügen

1. Öffne deinen Agenten und wechsle zum Tab **Leads**. **Webhooks** steht weiter unten im Tab, über **Slack alerts**.
2. Klicke auf **Add webhook**.
3. Füge unter **Endpoint URL** die Adresse ein, die dein Server oder dein Tool für eingehende Webhooks nennt.
4. Lass unter **Events to send** die Events angehakt, die du willst: **New lead**, **Hand-off requested**, **Form submitted**, **Meeting booked**, **Live chat requested** und **Return requested**. Bei einem neuen Webhook sind alle sechs angehakt. Ein früher angelegter Webhook behält seine Events. Klicke dort auf **Edit** und hake **Form submitted**, **Meeting booked**, **Live chat requested** oder **Return requested** an, wenn er diese bekommen soll.
5. Lass **Active** eingeschaltet und klicke auf **Add webhook**.
6. Kopiere das angezeigte Signing Secret, leg es dort ab, wo dein Empfänger es lesen kann, und klicke auf **I've saved it**.
7. Klicke auf **Send test event**, um die Verbindung zu prüfen. Das Ergebnis steht unter den Buttons.

Die Adresse muss mit `https://` beginnen und auf einen öffentlichen Server zeigen. Private und interne Netzwerkadressen wie `localhost` oder `192.168.1.10` werden beim Speichern abgelehnt und vor jeder Zustellung erneut geprüft. Jeder Agent kann bis zu 5 Webhooks haben.

Webhooks, die Zapier, Make oder dein eigener Code über die REST-API abonniert haben, zählen nicht zu den 5; siehe [Von Integrationen erstellte Webhooks](#von-integrationen-erstellte-webhooks). Ebenso wenig die Webhooks einzelner Formulare; siehe [Ein Webhook für ein einzelnes Formular](#ein-webhook-fur-ein-einzelnes-formular).

Um einen Webhook zu pausieren, ohne seine Einstellungen zu verlieren, schalte ihn aus. Er zeigt dann **Paused**. Events, die während der Pause passieren, werden nicht gesendet, auch nicht später. Eine Zustellung, die gerade auf eine Wiederholung wartet, schlägt beim nächsten Versuch fehl; ist der Webhook wieder aktiv, kannst du sie erneut senden. Mit **Edit** änderst du Adresse und Events. **Remove** löscht den Webhook samt Zustellverlauf.

## Von Integrationen erstellte Webhooks

Eine Integration kann über die [REST-API](/de/docs/rest-api#webhook-abonnieren) mit einem deiner API-Schlüssel einen Webhook für deinen Agenten abonnieren. Die [Apps für Zapier und Make](/de/docs/zapier-and-make) tun das, wenn du einen Zap einschaltest oder einem Szenario einen sofortigen Trigger hinzufügst.

Diese Webhooks stehen mit den anderen in der Karte **Webhooks**, mit einem Kennzeichen, das sagt, woher sie kommen: **via Zapier**, **via Make** oder **via API**. Sie werden zugestellt, signiert, wiederholt und protokolliert wie die Webhooks, die du selbst hinzufügst, und du kannst **Send test event**, **Recent deliveries**, den Schalter **Active** und **New secret** bei ihnen nutzen.

- Sie haben keinen Button **Edit**: Adresse und Events gehören dem Zap, dem Szenario oder dem Code, der sie angelegt hat, und eine Änderung hier würde ihn kaputt machen.
- **Remove** löscht einen davon. Der Zap oder das Szenario bleibt eingeschaltet, bekommt aber keine Events mehr; schalte ihn auch dort aus, oder schalte ihn aus und wieder ein, um einen neuen Webhook zu abonnieren.
- Die Integration entfernt ihren Webhook selbst, wenn du den Zap ausschaltest oder den Trigger löschst.
- Widerrufst du den API-Schlüssel, der sie erstellt hat, werden sie gelöscht. Siehe [Schlüssel widerrufen](/de/docs/rest-api#schlussel-widerrufen).
- Ein Agent kann bis zu 25 davon haben, zusätzlich zu den 5, die du selbst hinzufügst.

## Ein Webhook für ein einzelnes Formular

Ein [Chat-Formular](/de/docs/chat-forms#ein-webhook-fur-ein-einzelnes-formular) kann einen eigenen Webhook haben, der nur die Events `form.submitted` dieses Formulars bekommt. Du fügst ihn im Editor des Formulars im Tab **Actions** unter **Webhook for this form** hinzu und verwaltest ihn dort, nicht in der Karte **Webhooks**.

- Er hat ein eigenes Signing Secret, das nur einmal angezeigt wird, und wird genau wie die Webhooks hier zugestellt, signiert, wiederholt und protokolliert, mit demselben Rahmen und denselben Daten von `form.submitted`.
- Er sendet nur `form.submitted`, nur für sein Formular. Seine Events lassen sich nicht ändern, und ein Test-Event funktioniert wie unten beschrieben.
- Er zählt nicht zu den 5 Webhooks des Agenten und steht nicht in der Karte **Webhooks**.
- Die Webhooks des Agenten, die **Form submitted** empfangen, bekommen weiterhin die Einsendungen aller Formulare, auch die eines Formulars mit eigenem Webhook. Jeder Webhook bekommt eine eigene Zustellung mit eigener ID.
- Wird das Formular gelöscht, werden sein Webhook und dessen Zustellverlauf gelöscht.

## Das Signing Secret

Jeder Webhook hat ein eigenes Signing Secret, das mit `whsec_` beginnt. Vollständig siehst du es nur einmal, direkt nach dem Hinzufügen oder nach dem Erstellen eines neuen Secrets. Danach zeigt die Karte nur noch die letzten vier Zeichen.

Hast du das Secret verloren oder glaubst du, dass jemand anderes es kennt, klicke auf **New secret**. Das alte Secret funktioniert sofort nicht mehr, gib das neue also gleich an deinen Empfänger weiter.

## Was gesendet wird

Jede Zustellung ist eine `POST`-Anfrage mit JSON-Inhalt und diesen Headern:

| Header | Wert |
| --- | --- |
| `Content-Type` | `application/json` |
| `User-Agent` | `intoCHAT-Webhooks/1.0` |
| `X-IntoChat-Event` | Der Name des Events, etwa `lead.created` |
| `X-IntoChat-Delivery` | Die ID der Zustellung. Sie bleibt bei jeder Wiederholung gleich. |
| `X-IntoChat-Timestamp` | Wann dieser Versuch gesendet wurde, in Unix-Sekunden |
| `X-IntoChat-Signature` | `sha256=`, gefolgt von der Signatur, siehe unten |

Der Inhalt hat immer denselben Rahmen. `id` ist die ID der Zustellung, derselbe Wert wie `X-IntoChat-Delivery`:

```json
{
  "id": "DELIVERY_ID",
  "event": "lead.created",
  "created_at": "2026-10-07T09:30:00.000Z",
  "agent": { "id": "AGENT_ID", "name": "Acme Assistant" },
  "data": { }
}
```

### Daten von `lead.created`

| Feld | Inhalt |
| --- | --- |
| `leadId` | Die ID des Leads |
| `agentId`, `agentName` | Der Agent, der den Lead erfasst hat |
| `email` | Die E-Mail-Adresse oder `null` |
| `phone` | Die Telefonnummer oder `null` |
| `name` | Der Name aus dem Lead-Formular oder `null`. Aus einer Chat-Nachricht wird nie ein Name übernommen. |
| `customFields` | Die Antworten auf deine eigenen Felder als Liste von `{ "id", "label", "value" }`. Leer, wenn es keine gibt. |
| `source` | `form` oder `chat` |
| `conversationId` | Das Gespräch, aus dem der Lead stammt, oder `null` |
| `collectedAt` | Wann der Lead gespeichert wurde, als ISO-8601-Zeit in UTC |
| `identity` | Nur bei einem Besucher, der mit der [Identitätsprüfung](/de/docs/identity-verification) verifiziert wurde: `{ "userId", "name", "email" }`. Fehlt sonst. |

### Daten von `handoff.requested`

| Feld | Inhalt |
| --- | --- |
| `conversationId` | Das übergebene Gespräch |
| `visitorEmail` | Die Adresse, die der Besucher für die Antwort genannt hat |
| `summary` | Was der Besucher braucht, in den Worten des Agenten |
| `page` | Die Seite, auf der der Chat begann, oder `null` |
| `country` | Der zweistellige Ländercode des Besuchers oder `null` |
| `requestedAt` | Wann die Übergabe verschickt wurde, als ISO-8601-Zeit in UTC |
| `test` | `true` bei einer Übergabe aus dem Playground, sonst `false` |
| `ticket` | Nur wenn [Helpdesk-Tickets](/de/docs/helpdesk-tickets) für diese Übergabe ein Ticket geöffnet haben: `{ "provider", "id", "url" }`, wobei `provider` `zendesk`, `freshdesk` oder `hubspot` ist, `id` die Nummer des Tickets als String und `url` das Ticket in deinem Helpdesk öffnet. Fehlt sonst. |

### Daten von `form.submitted`

| Feld | Inhalt |
| --- | --- |
| `submissionId` | Die ID der Einsendung |
| `formId`, `formName` | Das abgeschickte Formular |
| `agentId`, `agentName` | Der Agent, der das Formular gezeigt hat |
| `answers` | Die Antworten des Besuchers als Liste von `{ "id", "label", "value" }`, in der Reihenfolge des Formulars. Leer gelassene optionale Felder fehlen. |
| `conversationId` | Das Gespräch, in dem das Formular abgeschickt wurde |
| `submittedAt` | Wann die Einsendung gespeichert wurde, als ISO-8601-Zeit in UTC |
| `identity` | Nur bei einem Besucher, der mit der [Identitätsprüfung](/de/docs/identity-verification) verifiziert wurde: `{ "userId", "name", "email" }`. Fehlt sonst. |

### Daten von `booking.created`

| Feld | Inhalt |
| --- | --- |
| `bookingId` | Die ID der Buchung in intoCHAT |
| `provider` | Der Kalender: `calcom` oder `calendly` |
| `providerBookingId` | Die ID der Buchung in Cal.com oder die URI des Eingeladenen in Calendly, zum Beispiel `https://api.calendly.com/scheduled_events/…/invitees/…` |
| `agentId`, `agentName` | Der Agent, bei dem der Termin gebucht wurde |
| `eventTypeId`, `eventTypeUri`, `eventTitle` | Der gebuchte Ereignistyp: `eventTypeId` ist die Nummer aus Cal.com, `eventTypeUri` die URI aus Calendly. Das jeweils andere Feld ist `null`. |
| `startAt`, `endAt` | Beginn und Ende des Termins, als ISO-8601-Zeiten in UTC |
| `timeZone` | Die Zeitzone des Besuchers, etwa `Europe/Berlin`, oder `UTC`, wenn sein Browser keine angegeben hat |
| `attendeeName`, `attendeeEmail` | Name und E-Mail-Adresse, die der Besucher eingegeben hat |
| `providerStatus` | `accepted` oder `pending`, wenn du die Buchung erst in Cal.com bestätigen musst. Calendly-Buchungen sind immer `accepted`. |
| `conversationId` | Das Gespräch, in dem der Termin gebucht wurde |
| `createdAt` | Wann die Buchung gespeichert wurde, als ISO-8601-Zeit in UTC |
| `identity` | Nur bei einem Besucher, der mit der [Identitätsprüfung](/de/docs/identity-verification) verifiziert wurde: `{ "userId", "name", "email" }`. Fehlt sonst. |

### Daten von `live_chat.requested`

| Feld | Inhalt |
| --- | --- |
| `conversationId` | Das Gespräch, in dem der Besucher nach einer Person gefragt hat |
| `visitorMessage` | Die letzte Nachricht des Besuchers, gekürzt auf 500 Zeichen mit `…` am Ende, wenn sie länger ist |
| `page` | Die Seite, auf der der Chat begann, oder `null` |
| `country` | Der zweistellige Ländercode des Besuchers oder `null` |
| `requestedAt` | Wann der Agent dein Team gebeten hat dazuzukommen, als ISO-8601-Zeit in UTC |

Das Event sagt nicht, ob jemand dazugekommen ist. Wer den Chat wann übernommen hat, siehst du im [Live-Chat-Posteingang](/de/docs/live-chat#der-live-chat-posteingang) und im Verlauf.

### Daten von `return.requested`

| Feld | Inhalt |
| --- | --- |
| `returnRequestId` | Die ID der Anfrage in intoCHAT |
| `provider` | Der Shop: `shopify` oder `woocommerce` |
| `orderName`, `orderId` | Die Bestellung, wie dein Shop sie anzeigt, zum Beispiel `#1042`, und ihre ID im Shop |
| `email` | Die E-Mail-Adresse der Bestellung. Der Besucher hat bewiesen, dass er sie kennt, oder die [Identitätsprüfung](/de/docs/identity-verification) deiner Website hat sie übergeben. |
| `kind` | `return` oder `exchange` |
| `items` | Die Artikel, jeweils als `{ "title", "quantity" }`. Der Titel enthält die Variante, wenn das Produkt in mehreren bestellt wurde, zum Beispiel `Wool scarf - Blue`. |
| `reason` | Der Grund, in den Worten des Besuchers |
| `status` | `requested` |
| `agentId`, `agentName` | Der Agent, bei dem die Anfrage gestellt wurde |
| `conversationId` | Das Gespräch, in dem die Anfrage gestellt wurde |
| `createdAt` | Wann die Anfrage gespeichert wurde, als ISO-8601-Zeit in UTC |

In deinem Shop wurde nichts genehmigt oder geändert. Wickle die Rücksendung dort ab und markiere sie dann in der Liste **Returns** im Tab **Leads** als erledigt.

Die Feldnamen aller Events nutzen `camelCase`.

### Test-Event

**Send test event** sendet eine Anfrage mit `"event": "test"` und diesen Daten: `{ "message": "This is a test event from intoCHAT. Your webhook is set up correctly." }`. Sie geht raus, egal welche Events der Webhook abonniert hat, auch wenn er pausiert ist, und wird nur einmal versucht. Weil ihre Daten anders aussehen als echte Events, braucht ein Tool, das Felder anhand eines Beispiels zuordnet, dafür ein echtes Event, etwa einen Lead, den du selbst auf deiner Website absendest.

## Signatur prüfen

Prüfe jede Anfrage, bevor du ihr vertraust. `X-IntoChat-Signature` ist `sha256=`, gefolgt vom hexadezimalen HMAC-SHA256 aus Zeitstempel, Punkt und unverändertem Inhalt, also `${timestamp}.${rawBody}`, mit dem Signing Secret des Webhooks als Schlüssel. Der Zeitstempel ist der Wert von `X-IntoChat-Timestamp`.

- Berechne die Signatur über den Inhalt genau so, wie er ankommt, bevor du das JSON auswertest.
- Vergleiche in konstanter Zeit.
- Lehne alte Zeitstempel ab, damit eine abgefangene Anfrage nicht erneut abgespielt werden kann. Das Beispiel unten erlaubt 5 Minuten.

Dieses Node.js-Beispiel ist dasselbe wie unter **Verify signatures** in der Karte **Webhooks**, dort mit einem **Copy**-Button:

```js
const crypto = require("crypto")

// rawBody: the request body exactly as received, before JSON parsing.
function verifyIntoChatWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-intochat-timestamp"]
  const signature = headers["x-intochat-signature"] || ""

  // Reject anything older than 5 minutes, so a captured request can't be replayed.
  const age = Math.abs(Date.now() / 1000 - Number(timestamp))
  if (!timestamp || !(age <= 300)) return false

  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex")

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

Kann dein Tool keine Signaturen prüfen, behandle die Webhook-Adresse wie ein Passwort: Wer sie kennt, kann Anfragen an sie senden.

## Antworten und Duplikate erkennen

- Antworte innerhalb von 10 Sekunden mit einem beliebigen `2xx`-Status. Alles andere gilt als Fehlschlag: ein anderer Statuscode, keine Antwort in der Zeit oder ein Verbindungs- oder Zertifikatsfehler.
- Weiterleitungen werden nicht verfolgt. Eine `3xx`-Antwort gilt als Fehlschlag, trag also die endgültige Adresse ein.
- Dasselbe Event kann mehr als einmal ankommen, etwa wenn dein Server es gespeichert, aber zu spät geantwortet hat. Nutze `X-IntoChat-Delivery` oder die `id` im Inhalt, um eine schon verarbeitete Zustellung zu ignorieren.
- Jeder Webhook bekommt für dasselbe Event eine eigene Zustellung mit eigener ID.

## Wiederholungen

- Der erste Versuch startet direkt nach dem Event. Schlägt er fehl, folgen innerhalb weniger Sekunden bis zu zwei schnelle Wiederholungen.
- Schlagen auch diese fehl, zeigt die Zustellung **Retrying**, und ein geplanter Job versucht es später erneut. Die Wiederholungen enden nach insgesamt 6 Versuchen oder 24 Stunden nach dem Event, je nachdem, was zuerst eintritt. Danach ist die Zustellung als **Failed** markiert.
- Der geplante Job läuft derzeit einmal am Tag. In der Praxis bekommt eine Zustellung, die nach den schnellen Wiederholungen noch fehlschlägt, ein oder zwei weitere Versuche, innerhalb von etwa einem Tag nach dem Event.
- Eine Zustellung an eine Adresse, die sich als privates Netzwerk herausstellt, schlägt sofort fehl, ohne Wiederholung.
- Jede Wiederholung behält die ID der Zustellung und wird mit neuem Zeitstempel neu signiert.

## Letzte Zustellungen

Klicke unter einem Webhook auf **Recent deliveries**, um seine letzten 20 Zustellungen zu sehen. Jede zeigt ihren Status (**Sending**, **Retrying**, **Delivered** oder **Failed**), das Event, die Uhrzeit, die Zahl der Versuche und den HTTP-Status. Eine Zustellung im Status Retrying zeigt, wann der nächste Versuch fällig ist, und eine nicht erfolgreiche zeigt den Fehler, etwa `No response within 10 seconds.`

Eine fehlgeschlagene Zustellung hat einen Button **Resend**, der sie sofort noch einmal versucht. Zustellungen werden nach 30 Tagen gelöscht.

## Wie es weitergeht

- Lege fest, was das Lead-Formular abfragt: [Lead-Erfassung](/de/docs/lead-collection).
- Lass Besucher dein Team per E-Mail erreichen: [Übergabe per E-Mail](/de/docs/handoff).
- Lass dein Team in den Chat kommen: [Live-Chat](/de/docs/live-chat).
- Frag Angaben mit einem Formular unter der Antwort des Agenten ab und gib einem Formular einen eigenen Webhook: [Chat-Formulare](/de/docs/chat-forms).
- Lass Besucher im Chat Termine buchen: [Terminbuchung mit Cal.com oder Calendly](/de/docs/booking).
- Beantworte Fragen zu Bestellungen und nimm Rücksendeanfragen an: [Bestellstatus und Rücksendungen](/de/docs/orders).
- Neue Leads, Übergaben und Live-Chat-Anfragen in einen Slack-Channel posten: [Slack-Benachrichtigungen](/de/docs/lead-alerts-and-export#slack-benachrichtigungen).
- intoCHAT in Zapier oder Make nutzen: [Zapier und Make](/de/docs/zapier-and-make).
- Eine Zustellung schlägt immer wieder fehl? Siehe [Fehlerbehebung](/de/docs/troubleshooting).
