Skip to content

Receiving events (webhooks)

A webhook sends a POST to your URL each time one of the events it subscribes to happens. Owners and admins create them in Settings → Webhooks. Zapier (“Webhooks by Zapier → Catch Hook”), Make and n8n can receive them directly.

Type When data
lead.received A lead arrives through POST /leads/intake lead, person, deal
person.created A contact is created person
deal.created A deal is created deal
deal.owner_changed A deal gets a new owner (deal.owner; from is the previous one) deal, from
deal.stage_changed A deal changes stage deal, from, to
deal.won A deal reaches a won stage (after deal.stage_changed) deal
deal.lost A deal reaches a lost stage (after deal.stage_changed) deal
unit.status_changed A unit becomes available, reserved or sold unit, from, to, deal
kit.viewed A client opens a shared sales kit (at most every 30 min) link, collection, deal

Records are snapshots taken when the event happened. They include names, emails, the unit’s place in the catalog (path) and unit type and a url to open it in the CRM, so most receivers don’t need to call back.

POST /your/endpoint
content-type: application/json
webhook-id: 01a1a2cb-a581-753c-97bd-3371de23fbc7
webhook-timestamp: 1793732617
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "8da75d2a-d5be-4037-9d9e-9311adcebe6b",
"type": "deal.won",
"timestamp": "2026-11-03T19:03:37.600Z",
"organizationId": "01a18e82-8246-7e43-82aa-5c246b073f08",
"data": {
"deal": {
"publicId": "DL-87",
"title": "A-101 · Ana López",
"amount": "3250000.00",
"currency": "MXN",
"stage": { "name": "Won", "kind": "won" },
"owner": { "name": "Carlos", "email": "carlos@example.com" },
"person": { "name": "Ana López", "email": "ana@example.com", "phone": "+52 998 123 4567" },
"units": [{ "name": "A-101", "path": ["Cancún", "Arbolada", "Tower A"], "inventoryStatus": "sold" }],
"url": "https://crm.example.com/deals/…"
}
}
}

Answer with any 2xx within 10 seconds. Anything else, or no answer, is retried with exponential backoff for several hours. Each attempt and the response you returned show up in the webhook’s delivery log, where a delivery can also be sent again.

Requests follow Standard Webhooks, so any of its libraries can check them with the webhook’s signing secret (whsec_…):

import { Webhook } from "standardwebhooks"
const webhook = new Webhook(process.env.CRM_WEBHOOK_SECRET)
// Throws if the signature is wrong or the timestamp is older than 5 minutes.
const event = webhook.verify(rawBody, request.headers)

Verify the raw body, before parsing it. Rotating the secret in the CRM invalidates the old one right away.

A delivery that is retried or sent again keeps its webhook-id. Store the ids you’ve processed and skip repeats. Deliveries can arrive out of order; use timestamp when order matters.