inDeal Webhooks: Lead- und Deal-Änderungen an andere Systeme senden

Lars Krüger

Lars Krüger

Zuletzt aktualisiert am Sep 26, 2026

Mit Webhooks meldet inDeal Lead- und Deal-Änderungen und neue Aktivitäten von sich aus an deine Systeme - statt dass du regelmäßig anfragen musst. Jede Änderung kommt als POST an eine URL deiner Wahl.

Inhalt

Webhook anlegen

  1. Öffne Einstellungen -> API & Webhooks und geh zum Abschnitt Webhooks.
  2. Klick auf Webhook hinzufügen. Trag einen Namen und die Ziel-URL ein - sie muss mit https:// beginnen und auf einen öffentlich erreichbaren Server zeigen. Adressen im internen Netz, localhost, abweichende Ports oder Umleitungen lehnt inDeal ab. Dein Empfänger muss dort POST-Anfragen annehmen und darf nicht weiterleiten.
  3. Alle neun Events sind vorausgewählt, vier für Leads und fünf für Deals. Willst du nur bestimmte empfangen, öffne Erweitert: Nur bestimmte Ereignisse senden und wähle die anderen ab.
  4. Nach dem Anlegen ist dein Webhook aktiv und du siehst dein Webhook-Secret. Wenn dein Empfänger prüfen soll, dass Anfragen wirklich von inDeal kommen, kopiere es jetzt - es wird nur einmal angezeigt. Für n8n, Zapier oder Make brauchst du es in der Regel nicht. Solltest du es später doch brauchen, erzeuge in der Liste mit Secret erneuern ein neues, siehe Secret erneuern.

Der Dialog zum Anlegen eines Webhooks mit Name und Ziel-URL

Die einmalige Anzeige deines Webhook-Secrets nach dem Anlegen

Bis zu 5 Webhooks sind möglich - zum Beispiel je angebundenem System einer.

Webhooks, die du angelegt hast, bevor es die Deal-Events gab, empfangen sie nicht automatisch. In der Liste steht dann Neu: Deal-Ereignisse verfügbar. - klick auf Bearbeiten und hake die Events an, die du brauchst.

Events

Event Wann
lead.created Ein neuer Lead entsteht, egal auf welchem Weg: von Hand, per Import, über die Erweiterung, per API oder aus Kampagnen
lead.updated Mindestens ein Lead-Feld oder ein eigenes Feld hat sich geändert
lead.stage_changed Die Phase hat gewechselt
activity.created Ein neuer Eintrag in der Timeline eines Leads oder Deals: Notiz, Anruf, E-Mail, Termin oder eine Antwort aus einer Kampagne - egal ob von Hand, per Import, aus dem E-Mail-Abgleich oder per API
deal.created Ein neuer Deal entsteht, in der App oder per API
deal.updated Mindestens ein Deal-Feld hat sich geändert (Wert, Wahrscheinlichkeit, Abschlussdatum, nächster Schritt, Notizen) oder ein eigenes Feld
deal.stage_changed Die Deal-Phase hat gewechselt, egal wohin
deal.won Der Deal wurde auf gewonnen gesetzt - kommt zusätzlich zu deal.stage_changed
deal.lost Der Deal wurde auf verloren gesetzt - kommt zusätzlich zu deal.stage_changed, mit dem Verlustgrund

Ein Phasenwechsel erzeugt nur lead.stage_changed beziehungsweise deal.stage_changed, kein zusätzliches lead.updated oder deal.updated und kein activity.created. Beim Abschluss eines Deals kommen zwei Zustellungen: deal.stage_changed und dazu deal.won oder deal.lost. Wenn dich nur der Abschluss interessiert, wähle die beiden letzten und lass deal.stage_changed weg.

Die Ereignis-Auswahl unter Erweitert mit den vier Lead- und fünf Deal-Ereignissen in zwei Spalten

Was ankommt

Jede Zustellung ist ein POST mit JSON-Body:

{
  "event": "lead.updated",
  "occurred_at": "2026-09-03T10:15:00+00:00",
  "lead_id": "0b0e...",
  "workspace_id": "aa70...",
  "data": {
    "id": "0b0e...",
    "full_name": "Julia Weber",
    "stage": "qualifying",
    "company_name": "Muster GmbH",
    "custom_data": {}
  },
  "changes": {
    "company_name": { "from": null, "to": "Muster GmbH" }
  }
}
  • data enthält den aktuellen Stand des Leads (dieselben Felder wie die API-Antwort).
  • changes unterscheidet sich je Event: bei lead.updated stehen dort nur die geänderten Felder mit altem und neuem Wert, bei lead.stage_changed steht { "stage": { "from": "qualifying", "to": "meeting_scheduled" } }, bei lead.created und activity.created ist es leer.
  • Zustellungen aus dem Test-Knopf tragen zusätzlich "test": true.

Bei activity.created kommt zusätzlich das Objekt activity mit - dieselben Felder wie beim Anlegen einer Aktivität per API. data ist auch hier der aktuelle Stand des Leads:

{
  "event": "activity.created",
  "occurred_at": "2026-09-03T14:00:00+00:00",
  "lead_id": "0b0e...",
  "workspace_id": "aa70...",
  "activity": {
    "id": "9f3a...",
    "type": "call",
    "title": "Kurzes Telefonat, Demo nächste Woche",
    "body": "Kurzes Telefonat, Demo nächste Woche",
    "quote": null,
    "channel": "call",
    "occurred_at": "2026-09-03T14:00:00+00:00",
    "created_at": "2026-09-03T14:00:01+00:00",
    "pinned": false,
    "edited_at": null
  },
  "data": {
    "id": "0b0e...",
    "full_name": "Julia Weber",
    "stage": "qualifying",
    "company_name": "Muster GmbH",
    "custom_data": {}
  },
  "changes": {}
}

type ist einer von note, call, email, meeting_scheduled oder reply. Bei reply (Antwort aus einer Kampagne) steht der Antworttext in quote.

Das Feld entity sagt, woran der Eintrag hängt: lead oder deal. Bei einem Eintrag am Deal ist deal_id gesetzt, lead_id ist leer und data ist der aktuelle Stand des Deals.

Deal-Events sehen genauso aus, nur mit deal_id und dem Deal in data - dieselben Felder wie die Deal-Antwort der API, inklusive des zugehörigen Leads. Die Werte eigener Felder stehen bei Leads und Deals in custom_data, der Schlüssel ist der API-Name des Feldes. Ändert sich ein eigenes Feld, kommt lead.updated bzw. deal.updated. Ein Beispiel für ein Deal-Event:

{
  "event": "deal.lost",
  "occurred_at": "2026-09-05T09:30:00+00:00",
  "deal_id": "7c1d...",
  "lead_id": "0b0e...",
  "workspace_id": "aa70...",
  "data": {
    "id": "7c1d...",
    "lead_id": "0b0e...",
    "stage": "lost",
    "value": 12000,
    "confidence": 40,
    "weighted_value": 0,
    "lost_reason": "Budget gestrichen",
    "won_at": null,
    "lost_at": "2026-09-05T09:30:00+00:00",
    "lead": { "id": "0b0e...", "full_name": "Julia Weber", "company_name": "Muster GmbH", "email": "julia.weber@muster.de" }
  },
  "changes": {
    "stage": { "from": "send_offer", "to": "lost" },
    "lost_reason": { "from": null, "to": "Budget gestrichen" }
  }
}
  • Bei deal.updated stehen in changes nur die geänderten Felder mit altem und neuem Wert.
  • Bei deal.stage_changed und deal.won steht dort der Phasenwechsel, bei deal.lost zusätzlich lost_reason.
  • Bei deal.created ist changes leer.

Dazu kommen diese Header:

Header Inhalt
X-Indeal-Signature sha256=... - die Signatur, siehe unten
X-Indeal-Timestamp Unix-Sekunden des Versands
X-Indeal-Event der Event-Typ
X-Indeal-Delivery eindeutige ID der Zustellung

Signatur prüfen

Mit der Signatur stellst du sicher, dass die Anfrage wirklich von inDeal kommt. Signiert wird der Text timestamp.body mit deinem Secret (HMAC-SHA256). Prüfe mit einem zeitkonstanten Vergleich und akzeptiere nur Timestamps, die höchstens 5 Minuten alt sind.

Node.js:

const { createHmac, timingSafeEqual } = require("node:crypto");

function verify(secret, req, rawBody) {
  const ts = Number(req.headers["x-indeal-timestamp"]);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = "sha256=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const got = req.headers["x-indeal-signature"] ?? "";
  const a = Buffer.from(expected);
  const b = Buffer.from(got);
  return a.length === b.length && timingSafeEqual(a, b);
}

Python:

import hashlib, hmac, time

def verify(secret: str, timestamp: str, raw_body: bytes, signature: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:
        return False
    mac = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256)
    return hmac.compare_digest("sha256=" + mac.hexdigest(), signature)

Wichtig: prüfe gegen den ROHEN Anfragetext, nicht gegen den neu serialisierten JSON-Body - sonst schlägt der Vergleich fehl.

Zustellung und Wiederholung

Antworte mit einem Status zwischen 200 und 299. Alles andere, auch keine Antwort innerhalb von 10 Sekunden, zählt als Fehlversuch.

  • inDeal versucht jede Zustellung bis zu 8 Mal, mit wachsendem Abstand: 1, 2, 4, 8, 16, 32, 64 Minuten.
  • Nach dem achten Fehlversuch gilt die Zustellung als endgültig gescheitert. Du siehst sie im Aufklapper Letzte Zustellungen und kannst sie dort mit Erneut senden noch einmal anstoßen.
  • Schlagen 50 Zustellungen in Folge fehl, deaktiviert inDeal den Webhook automatisch, siehe Wenn dein Endpoint deaktiviert wurde. Ab 5 Fehlversuchen in Folge zeigt die Liste schon einen gelben Hinweis.
  • Selten kann eine Zustellung doppelt ankommen. Nutze den Header X-Indeal-Delivery, um Doppelte zu erkennen.

Wenn dein Endpoint deaktiviert wurde

Schlagen 50 Zustellungen in Folge fehl, schaltet inDeal den Webhook ab. Ab dann werden keine Ereignisse mehr an diese Adresse geschickt, auch keine neuen.

  • Du erfährst es sofort. Alle Admins des Accounts bekommen eine E-Mail mit Name, Ziel-URL, dem letzten Fehler in Klartext und dem Zeitpunkt. In der Webhook-Liste steht der Webhook rot als Deaktiviert mit demselben Grund.
  • Ursache beheben. Der häufigste Fall: der Empfänger antwortet mit 404 oder 405, weil die URL nicht mehr stimmt oder kein POST annimmt. Bei n8n ist das typischerweise eine Test-URL mit /webhook-test/ im Pfad. Sie funktioniert nur, solange der Workflow auf ein Test-Ereignis wartet. Für den Dauerbetrieb brauchst du die Production-URL des aktivierten Workflows, sie enthält /webhook/ ohne -test.
  • Wieder aktivieren. Klick in der Liste auf Wieder aktivieren. Der Fehlerzähler startet bei null, und neue Ereignisse werden wieder zugestellt. Zustellungen, die in der Zwischenzeit endgültig gescheitert sind, kannst du im Aufklapper Letzte Zustellungen mit Erneut senden nachholen.

Testen und erneut senden

  • Test senden erzeugt eine Beispiel-Zustellung vom Typ lead.created mit "test": true im Body. Sie läuft durch denselben Weg wie echte Zustellungen und erscheint innerhalb einer Minute beim Empfänger. So prüfst du Erreichbarkeit und Signatur, ohne einen echten Lead anzulegen. Bis zu 5 Tests pro Minute und Webhook sind möglich, und nur bei aktivem Webhook.
  • Der Aufklapper Letzte Zustellungen zeigt je Zustellung Status, Event, Versuche, HTTP-Code und Zeitpunkt. Erneut senden stößt eine noch nicht zugestellte oder endgültig gescheiterte Zustellung sofort wieder an.

Deine Webhook-Zeile mit aufgeklappten Zustellungen: zwei erfolgreiche und eine endgültig gescheiterte mit Fehlertext

Secret erneuern

Das Secret wird nur einmal angezeigt. Hast du es verloren, oder willst du es turnusmäßig austauschen, brauchst du den Webhook nicht neu anzulegen:

  1. Klick in der Webhook-Zeile auf Secret erneuern und bestätige.
  2. inDeal zeigt dir das neue Secret genau einmal. Kopiere es und hinterlege es bei deinem Empfänger.

Das alte Secret ist ab dem Klick ungültig. Zustellungen, die danach rausgehen, sind mit dem neuen Secret signiert - trägt dein Empfänger noch das alte, schlägt seine Signaturprüfung fehl, bis du es dort ausgetauscht hast. Name, URL, Events und die Zustell-Historie bleiben unverändert. Prüft dein Empfänger die Signatur nicht (zum Beispiel Zapier, Make oder n8n ohne Code-Schritt), merkt er von der Erneuerung nichts.

Zapier, Make und n8n

Alle drei Werkzeuge bieten Webhook-Trigger, die eine URL erzeugen:

  1. Lege dort einen Webhook-Trigger an (bei Zapier "Catch Hook", bei Make und n8n einen Webhook-Knoten) und kopiere die erzeugte URL.
  2. Trag diese URL als Ziel-URL in inDeal ein. Die Events lässt du am besten alle ausgewählt und filterst im Werkzeug nach dem Feld event.
  3. Klick auf Test senden und bau den Rest deines Ablaufs mit dem empfangenen Beispiel auf.

Die Signaturprüfung ist bei diesen Werkzeugen optional - die URLs sind lang und zufällig, das Secret brauchst du in der Regel nicht. Wenn du trotzdem prüfen willst, nutze einen Code-Schritt mit dem Beispiel von oben.

Weiter