CRM-Events abonnieren, Signaturen verifizieren, fehlgeschlagene Deliveries debuggen, und der volle Event-Katalog auf einer Seite.
Events wählen, HTTPS-URL geben, wir POST-en JSON dorthin bei jeder Änderung. Fehlgeschlagene Deliveries aus der UI oder per MCP erneut zustellen. Signaturen per HMAC-SHA256 über den Raw-Body prüfen. Jedes Event mit Payload-Shape steht unten auf dieser Seite.
Webhooks machen dein CRM zur Source-of-Truth, auf die dein restlicher Stack lauscht. Kein Polling, keine stale Daten.
Typische Einsätze:
UI: Unternehmen → Webhooks → Neu.
MCP: create_webhook mit url, events[], optional description, secret, enabled.
{
"url": "https://hooks.example.com/customermates",
"events": ["contact.created", "contact.updated", "deal.updated"],
"secret": "nutze-einen-random-string-aus-einem-password-manager",
"enabled": true
}Die URL muss HTTPS sein. HTTP wird abgelehnt.
Jede Delivery ist ein POST mit Content-Type: application/json. Jede Delivery folgt demselben Envelope:
{
"event": "<event-name>",
"data": {
"userId": "<verursacher>",
"companyId": "<tenant>",
"entityId": "<betroffener-record>",
"payload": { /* event-spezifisch */ }
},
"timestamp": "<iso-8601>"
}Die userId ist der User, der den Write verursacht hat, inkl. User, die über einen API-Key handeln.
Der changes-Record existiert nur bei *.updated-Events. Er listet jedes Feld, das sich wirklich geändert hat, mit vorherigem und aktuellem Wert.
Beispiel, contact.updated:
{
"event": "contact.updated",
"data": {
"userId": "u_123",
"companyId": "c_abc",
"entityId": "ct_xyz",
"payload": {
"contact": {
"id": "ct_xyz",
"firstName": "Max",
"lastName": "Mustermann",
"notes": { /* Tiptap JSON */ },
"organizationIds": ["org_1"],
"userIds": [],
"dealIds": ["deal_1", "deal_2"],
"customFieldValues": [
{ "columnId": "col_stage", "value": "won" }
]
},
"changes": {
"organizationIds": {
"previous": [],
"current": ["org_1"]
}
}
}
},
"timestamp": "2026-04-22T10:00:00.000Z"
}changes-RecordJeder Key in changes ist ein Feldname, dessen Wert sich bewegt hat. previous ist der vorherige, current der aktuelle.
Relationship-ID-Arrays vergleichen sich als Sets. Objekte als Deep-Equal. Skalare per Wert.
Wenn ein Write tatsächlich nichts ändert, wird das Event unterdrückt. Du bekommst keine No-op-*.updated-Events.
Wenn du ein secret setzt, enthält jede ausgehende Request:
X-Webhook-Signature: <hex>Wo <hex> = HMAC-SHA256(secret, rawRequestBody), Lowercase-Hex, kein Präfix. Auf deiner Seite neu berechnen und constant-time vergleichen.
Node.js:
import crypto from "crypto";
function verify(rawBody: string, received: string, secret: string) {
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}Jede Delivery wird geloggt. Sichtbar in der UI (Unternehmen → Webhook-Deliveries) oder via list_webhook_deliveries.
Eine Delivery ist failed, wenn der HTTP-Status nicht im 2xx-Bereich liegt oder die Request timeoutet.
Failed Delivery neu zustellen via UI oder resend_webhook_delivery mit der Delivery-ID. Ein neuer Delivery-Record entsteht; das Original bleibt unverändert.
Customermates retryt fehlgeschlagene Deliveries nicht automatisch. Wenn dein Receiver down war, Retries aus der UI anstoßen oder list_webhook_deliveries in ein Monitoring-Skript piepen.
Deliveries sind nicht strikt geordnet. Zwei contact.updated-Events im Millisekundenabstand können out-of-order ankommen. Per Timestamp auflösen, oder Deliveries als idempotente Messages behandeln, geschlüsselt per entityId.
Das notes-Feld in Payloads ist Tiptap-JSON, dieselbe Struktur, die der Editor speichert. Zum Rendern als Markdown auf deiner Seite einen Tiptap-Serializer einsetzen. In MCP-Tools liefert get_entities mit include: "withNotes" die Notes bereits als Markdown.
Delete-Events enthalten nur die ID, nicht den Endzustand. Wenn du den Endzustand brauchst (fürs Archiv z.B.), auch *.updated abonnieren und einen lokalen Mirror halten.
list_webhook_deliveries unterstützt searchTerm auf URL und Event-Name.15 Events über die fünf Entity-Typen, alle mit dem Envelope oben.
| Event | Wann es feuert | Payload-Keys |
|---|---|---|
contact.created | Contact via UI/API/MCP/Import erstellt | contact |
contact.updated | Irgendein Contact-Feld ändert sich | contact, changes |
contact.deleted | Contact gelöscht | contactId |
organization.created | Organization erstellt | organization |
organization.updated | Irgendein Organization-Feld ändert sich | organization, changes |
organization.deleted | Organization gelöscht | organizationId |
deal.created | Deal erstellt | deal |
deal.updated | Irgendein Deal-Feld ändert sich | deal, changes |
deal.deleted | Deal gelöscht | dealId |
service.created | Service erstellt | service |
service.updated | Irgendein Service-Feld ändert sich | service, changes |
service.deleted | Service gelöscht | serviceId |
task.created | Task erstellt | task |
task.updated | Irgendein Task-Feld ändert sich | task, changes |
task.deleted | Task gelöscht | taskId |