CRM-Events abonnieren, Signaturen verifizieren, fehlgeschlagene Deliveries prüfen und den vollständigen Event-Katalog auf einer Seite lesen.
Customermates sendet einen JSON-POST an Ihren HTTPS-Endpunkt, sobald sich abonnierte Daten ändern. Sie wählen die Events und geben die URL an. Fehlgeschlagene Deliveries können Sie aus der UI oder per MCP erneut zustellen. Deliveries werden per HMAC-SHA256 über den rohen Request-Body signiert. Jedes Event und seine Payload-Struktur stehen im Katalog am Ende dieser Seite.
Webhooks lassen andere Systeme auf CRM-Änderungen reagieren, ohne zu pollen.
UI: Unternehmen, dann Webhooks, dann Neu.
MCP: manage_webhooks mit action: "create", dazu url, events[] und 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 und demselben Envelope:
{
"event": "<event-name>",
"data": {
"userId": "<verursacher>",
"companyId": "<workspace>",
"entityId": "<betroffener-record>",
"payload": { /* event-spezifisch */ }
},
"timestamp": "<iso-8601>"
}userId ist der User, der den Write verursacht hat, inklusive User, die über einen API-Key handeln. Bei Messaging-Events, die durch eingehende Provider-Aktivität statt durch eine User-Aktion ausgelöst werden, ist userId gleich null.
Bei *.updated-Events umschließt payload den vollständigen Record zusammen mit einem changes-Objekt. Bei *.created- und *.deleted-Events ist payload der Record selbst.
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 */ },
"organizations": [{ "id": "org_1", "name": "Example GmbH" }],
"users": [],
"deals": [{ "id": "deal_1" }, { "id": "deal_2" }],
"customFieldValues": [
{ "columnId": "col_abc", "value": "Won" }
]
},
"changes": {
"organizations": {
"previous": [],
"current": [{ "id": "org_1", "name": "Example GmbH" }]
}
}
}
},
"timestamp": "2026-04-22T10:00:00.000Z"
}Die Record-Felder hängen vom Entity-Typ ab. Custom-Columns erscheinen unter customFieldValues als columnId- und value-Paare. Mit get_record_schema sehen Sie die Columns, die für einen Entity-Typ in Ihrem Workspace konfiguriert sind.
changes-Objektchanges existiert nur bei *.updated-Events. Jeder Key ist ein Record-Feld, dessen Wert sich geändert hat. previous ist der Wert vor dem Write, current der Wert danach.
Arrays verknüpfter Records werden per id verglichen. Objekte per Deep-Equal. Skalare Felder per Wert. createdAt und updatedAt werden nie als Änderung gemeldet.
Wenn ein Write kein Feld ändert, wird das Event unterdrückt. No-op-*.updated-Events werden nicht zugestellt.
Wenn Sie ein secret setzen, enthält jede Request:
X-Webhook-Signature: <hex><hex> ist HMAC-SHA256(secret, rawRequestBody), Lowercase-Hex, ohne Präfix. Berechnen Sie den Wert auf Ihrer Seite neu und vergleichen Sie in konstanter Zeit.
Node.js-Beispiel:
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 protokolliert. Deliveries sehen Sie in der UI (Unternehmen, dann Webhook-Deliveries) oder per manage_webhooks mit action: "list_deliveries".
Eine Delivery gilt als fehlgeschlagen, wenn der HTTP-Status außerhalb des 2xx-Bereichs liegt oder die Request timeoutet. Der Request-Timeout beträgt 5 Sekunden.
Customermates wiederholt fehlgeschlagene Deliveries automatisch: bis zu 5 Wiederholungen bei Timeouts, 5xx-Responses und 408, 425 und 429. Dauerhafte 4xx-Responses (zum Beispiel 400, 401, 403, 404) werden nicht wiederholt, weil ein erneuter Versuch genauso scheitern würde. Die Retries laufen innerhalb des Delivery-Runs, erzeugen also keine zusätzlichen Delivery-Records; jeder Record zeigt das Endergebnis.
Sie können jede Delivery zusätzlich selbst erneut zustellen, aus der UI oder per manage_webhooks mit action: "resend_delivery" und der Delivery-id. Ein manueller Resend erzeugt einen neuen Delivery-Record; das Original bleibt unverändert.
Deliveries sind über Events hinweg nicht strikt geordnet. Zwei contact.updated-Events im Millisekundenabstand können in falscher Reihenfolge ankommen. Löse die Reihenfolge über den timestamp im Payload auf oder behandle Deliveries als idempotente Messages mit entityId als Schlüssel.
Das notes-Feld in Payloads ist Tiptap-JSON, dieselbe Struktur, die der Editor speichert. Um es auf Ihrer Seite als Markdown zu rendern, nutzen Sie einen Tiptap-kompatiblen Serializer. In MCP liefert get_records mit include: "withNotes" die Notes bereits als Markdown serialisiert.
Delete-Events enthalten unter payload den vollständigen Record, wie er vor dem Löschen war, nicht nur die id. Die id des betroffenen Records steht zusätzlich in data.entityId.
manage_webhooks, action: "list_deliveries") unterstützt searchTerm auf URL und Event-Name.Sechsundzwanzig Events können Sie abonnieren: fünfzehn Record-Events über die fünf Entity-Typen und elf Messaging-Events. Alle nutzen den Envelope oben.
| Event | Wann es feuert | payload |
|---|---|---|
contact.created | Ein Contact wird via UI, API, MCP oder Import erstellt | vollständiger Contact |
contact.updated | Ein Contact-Feld ändert sich | contact, changes |
contact.deleted | Ein Contact wird gelöscht | vollständiger Contact |
organization.created | Eine Organization wird erstellt | vollständige Organization |
organization.updated | Ein Organization-Feld ändert sich | organization, changes |
organization.deleted | Eine Organization wird gelöscht | vollständige Organization |
deal.created | Ein Deal wird erstellt | vollständiger Deal |
deal.updated | Ein Deal-Feld ändert sich | deal, changes |
deal.deleted | Ein Deal wird gelöscht | vollständiger Deal |
service.created | Ein Service wird erstellt | vollständiger Service |
service.updated | Ein Service-Feld ändert sich | service, changes |
service.deleted | Ein Service wird gelöscht | vollständiger Service |
task.created | Ein Task wird erstellt | vollständiger Task |
task.updated | Ein Task-Feld ändert sich | task, changes |
task.deleted | Ein Task wird gelöscht | vollständiger Task |
Messaging-Events feuern bei eingehender Provider-Aktivität auf verbundenen Accounts. userId ist null. Payloads referenzieren Provider-Records per id (connectedAccountId, provider, providerMessageId, threadId und ähnliche), statt CRM-Records einzubetten.
| Event | Wann es feuert |
|---|---|
messaging.message.received | Eine Chat-Nachricht oder E-Mail wird ingested |
messaging.message.updated | Eine ingestete Nachricht wird aktualisiert |
messaging.message.deleted | Eine ingestete Nachricht wird gelöscht |
messaging.message.reaction | Eine Reaktion wird zu einer Nachricht hinzugefügt |
messaging.email.received | Eine E-Mail wird ingested |
messaging.email.deleted | Eine ingestete E-Mail wird gelöscht |
messaging.chat.updated | Ein Chat-Thread wird aktualisiert |
messaging.chat.deleted | Ein Chat-Thread wird gelöscht |
messaging.calendar.changed | Ein verbundener Kalender ändert sich |
messaging.calendar_event.changed | Ein Kalender-Event ändert sich |
messaging.relation.created | Eine neue Provider-Relation wird erstellt |