Skip to main content
Nineteen events, grouped by the resource they are about. Each resource has an overview naming its triggers, and each trigger has a page with its exact payload, worked examples, and the cases that catch people out.

Matching several events

A subscription holds a list of patterns, and an event is delivered if any of them match.
* is convenient and it is also a standing agreement to receive events that do not exist yet. Switch on the event field rather than assuming the set, and ignore anything you do not recognise instead of throwing. A handler that 500s on an unknown event turns a new feature into a retry storm against your own server.
One action can fire more than one event. A public form submission creates a record, so it delivers form.submitted and record.created with the same recordId. Subscribing to * or to both means deduplicating on data.recordId, or branching on event.Incoming mail does the same: every incoming message delivers email.received and email.replied, with the same messageId.

The catalog is exactly what emits

Every event listed here has a live emitter behind it. That is enforced by a test rather than by convention, because an event offered in the picker with nothing emitting it is, from your side, indistinguishable from a webhook that is broken. Each one travels Custral’s public event pipeline: the one that writes the Events log, runs workflow triggers, and fans out to webhooks. Some conversation changes still have no event of their own: a conversation being created, reopened, or offered to several people before anybody owns it. If you need one of them, say which at hello@custral.com. A public event name is a wire contract the moment you subscribe to it, so it is mapped rather than passed straight through, and it does not move when an internal emitter is renamed or refactored.

Bulk writes do not fire webhooks

Imports, job runs, AI property fills and other automated bulk writes are suppressed. One import of ten thousand rows would otherwise be ten thousand deliveries. Deleting several records from a table at once still delivers one record.deleted per record, and deleting several conversations delivers one conversation.deleted each. That is a deliberate trade rather than an oversight, and it means a webhook is not a complete audit trail of everything that changed. If you need to see bulk changes, reconcile against GET /v1/records/{object} sorted by the updated timestamp.
Need webhooks from an import specifically? Tell us at hello@custral.com. It needs its own opt-in rather than a quiet widening of this rule, so it is worth knowing somebody wants it.

Field values are ids

Select, status, relation and user values arrive as stored ids (["opt_3Kd8sM"]), not labels. See Payload format for how to resolve them.