Subscribe to CRM events, verify signatures, debug failed deliveries, and the full event catalog in one place.
Pick events, give us an HTTPS URL, we POST JSON there every time something changes. Re-send failed deliveries from the UI or through MCP. Verify signatures with HMAC-SHA256 over the raw body. Every event with its payload shape is listed at the bottom of this page.
Webhooks turn your CRM into the source of truth your other tools listen to. No polling, no stale data.
Common uses:
UI: Company → Webhooks → New.
MCP: create_webhook with url, events[], optional description, secret, enabled.
{
"url": "https://hooks.example.com/customermates",
"events": ["contact.created", "contact.updated", "deal.updated"],
"secret": "use-a-random-string-from-a-password-manager",
"enabled": true
}The URL must be HTTPS. HTTP is rejected.
Every delivery is a POST with Content-Type: application/json. Every delivery follows the same envelope:
{
"event": "<event-name>",
"data": {
"userId": "<who-triggered-it>",
"companyId": "<tenant>",
"entityId": "<affected-record>",
"payload": { /* event-specific */ }
},
"timestamp": "<iso-8601>"
}The userId is the user that caused the write, which includes a user acting through an API key.
The changes record is only present on *.updated events. It names every field that actually moved, with previous and current values.
Example, 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 recordEach key in changes is a field name whose value moved. previous is what was there before, current is what is there now.
Arrays of relationship ids compare as sets. Objects compare by deep equality. Scalar fields compare by value.
If a write does not actually change any field, the event is suppressed. You will not see no-op *.updated events.
If you set a secret, every outgoing request includes:
X-Webhook-Signature: <hex>Where <hex> is HMAC-SHA256(secret, rawRequestBody), lowercase hex, no prefix. Recompute on your side and compare in constant time.
Node.js example:
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));
}Every delivery is recorded. See them in the UI (Company → Webhook Deliveries) or through list_webhook_deliveries.
A delivery is considered failed if the HTTP status is outside the 2xx range or the request times out.
Retry a failed delivery through the UI or resend_webhook_delivery with the delivery id. A new delivery record is created; the original is unchanged.
Customermates does not automatically retry failed deliveries. If your receiver is down, trigger retries from the UI or pipe list_webhook_deliveries through a monitoring script.
Deliveries are not strictly ordered across events. Two contact.updated events a millisecond apart can arrive out of order. Use the timestamp in the payload to resolve, or treat deliveries as idempotent messages keyed by entityId.
The notes field in payloads is Tiptap JSON, the same structure the editor stores. To render as markdown on your side, run it through a Tiptap-compatible serializer. In MCP tools, get_entities with include: "withNotes" returns notes pre-serialized to markdown.
Delete events carry only the id, not the final state of the record. If you need the final state for a delete hook (for archiving, say), subscribe to *.updated as well and keep a local mirror.
list_webhook_deliveries tool supports searchTerm on url and event name.15 events across the five entity types, all with the envelope above.
| Event | When it fires | Payload keys |
|---|---|---|
contact.created | A contact is created via UI, API, MCP, or import | contact |
contact.updated | Any contact field changes | contact, changes |
contact.deleted | A contact is deleted | contactId |
organization.created | An organization is created | organization |
organization.updated | Any organization field changes | organization, changes |
organization.deleted | An organization is deleted | organizationId |
deal.created | A deal is created | deal |
deal.updated | Any deal field changes | deal, changes |
deal.deleted | A deal is deleted | dealId |
service.created | A service is created | service |
service.updated | Any service field changes | service, changes |
service.deleted | A service is deleted | serviceId |
task.created | A task is created | task |
task.updated | Any task field changes | task, changes |
task.deleted | A task is deleted | taskId |