Subscribe to CRM events, verify signatures, inspect failed deliveries, and read the full event catalog in one place.
Customermates sends a JSON POST to your HTTPS endpoint whenever subscribed data changes. You choose the events and provide the URL. Failed deliveries can be re-sent from the UI or through MCP. Deliveries are signed with HMAC-SHA256 over the raw request body. Every event and its payload shape is listed in the catalog at the end of this page.
Webhooks let other systems react to CRM changes without polling.
UI: Company, then Webhooks, then New.
MCP: manage_webhooks with action: "create", plus url, events[], and 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 and the same envelope:
{
"event": "<event-name>",
"data": {
"userId": "<who-triggered-it>",
"companyId": "<workspace>",
"entityId": "<affected-record>",
"payload": { /* event-specific */ }
},
"timestamp": "<iso-8601>"
}userId is the user who caused the write, including a user acting through an API key. For messaging events, which are triggered by inbound provider activity rather than a user action, userId is null.
For *.updated events, payload wraps the full record together with a changes object. For *.created and *.deleted events, payload is the record itself.
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 */ },
"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"
}The record fields depend on the entity type. Custom columns appear under customFieldValues as columnId and value pairs. Use get_record_schema to see the columns configured for a given entity in your workspace.
changes objectchanges is present only on *.updated events. Each key is a record field whose value moved. previous is the value before the write, current is the value after.
Arrays of related records compare by id. Objects compare by deep equality. Scalar fields compare by value. createdAt and updatedAt are never reported as changes.
If a write does not change any field, the event is suppressed. No-op *.updated events are not delivered.
If you set a secret, every request includes:
X-Webhook-Signature: <hex><hex> is HMAC-SHA256(secret, rawRequestBody), lowercase hex, no prefix. Recompute it 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. Review deliveries in the UI (Company, then Webhook Deliveries) or through manage_webhooks with action: "list_deliveries".
A delivery is marked failed if the HTTP status is outside the 2xx range or the request times out. The request timeout is 5 seconds.
Customermates automatically retries failed deliveries: up to 5 retries for timeouts, 5xx responses, and 408, 425, and 429. Permanent 4xx responses (for example 400, 401, 403, 404) are not retried, because a repeat would fail the same way. Retries happen inside the delivery run, so they do not create extra delivery records; each record shows the final outcome.
You can also resend any delivery yourself from the UI or with manage_webhooks and action: "resend_delivery" plus the delivery id. A manual resend creates a new delivery record; the original is unchanged.
Deliveries are not strictly ordered across events. Two contact.updated events milliseconds apart can arrive out of order. Use the timestamp in the payload to resolve order, 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 it as markdown on your side, run it through a Tiptap-compatible serializer. In MCP, get_records with include: "withNotes" returns notes already serialized to markdown.
Delete events carry the full record as it was before deletion, under payload, not just the id. The affected record id is also available as data.entityId.
manage_webhooks, action: "list_deliveries") supports searchTerm on url and event name.Twenty-six events are available to subscribe to: fifteen record events across the five entity types, and eleven messaging events. All use the envelope above.
| Event | When it fires | payload |
|---|---|---|
contact.created | A contact is created via UI, API, MCP, or import | full contact |
contact.updated | Any contact field changes | contact, changes |
contact.deleted | A contact is deleted | full contact |
organization.created | An organization is created | full organization |
organization.updated | Any organization field changes | organization, changes |
organization.deleted | An organization is deleted | full organization |
deal.created | A deal is created | full deal |
deal.updated | Any deal field changes | deal, changes |
deal.deleted | A deal is deleted | full deal |
service.created | A service is created | full service |
service.updated | Any service field changes | service, changes |
service.deleted | A service is deleted | full service |
task.created | A task is created | full task |
task.updated | Any task field changes | task, changes |
task.deleted | A task is deleted | full task |
Messaging events fire on inbound provider activity on connected accounts. userId is null. Payloads reference provider records by id (connectedAccountId, provider, providerMessageId, threadId, and similar) rather than embedding CRM records.
| Event | When it fires |
|---|---|
messaging.message.received | A chat message or email is ingested |
messaging.message.updated | An ingested message is updated |
messaging.message.deleted | An ingested message is deleted |
messaging.message.reaction | A reaction is added to a message |
messaging.email.received | An email is ingested |
messaging.email.deleted | An ingested email is deleted |
messaging.chat.updated | A chat thread is updated |
messaging.chat.deleted | A chat thread is deleted |
messaging.calendar.changed | A connected calendar changes |
messaging.calendar_event.changed | A calendar event changes |
messaging.relation.created | A new provider relation is created |