> ## Documentation Index
> Fetch the complete documentation index at: https://docs.custral.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Event catalog

> Every event you can subscribe to, what makes it fire, and how to match several at once.

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.

| Resource                                            | Event                                                                             | Fires when                                                             |
| --------------------------------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [Records](/dev/webhooks/events/records)             | [`record.created`](/dev/webhooks/events/record-created)                           | A record is created on any object                                      |
|                                                     | [`record.updated`](/dev/webhooks/events/record-updated)                           | One field on a record changes                                          |
|                                                     | [`record.deleted`](/dev/webhooks/events/record-deleted)                           | A record is deleted from any object                                    |
| [Forms](/dev/webhooks/events/forms)                 | [`form.submitted`](/dev/webhooks/events/form-submitted)                           | A form submission creates a record                                     |
| [Tasks](/dev/webhooks/events/tasks)                 | [`task.completed`](/dev/webhooks/events/task-completed)                           | A task is marked done                                                  |
| [Email](/dev/webhooks/events/email)                 | [`email.received`](/dev/webhooks/events/email-received)                           | A message from outside the workspace arrives, in a conversation thread |
|                                                     | [`email.replied`](/dev/webhooks/events/email-replied)                             | Every incoming message, alongside `email.received`                     |
|                                                     | [`email.sent`](/dev/webhooks/events/email-sent)                                   | A message is sent from a connected mailbox, in a conversation thread   |
|                                                     | [`email.opened`](/dev/webhooks/events/email-opened)                               | Mail a workflow or sequence sent is opened                             |
|                                                     | [`email.clicked`](/dev/webhooks/events/email-clicked)                             | A tracked link in that mail is clicked                                 |
|                                                     | [`email.bounced`](/dev/webhooks/events/email-bounced)                             | A sent message bounces                                                 |
|                                                     | [`email.unsubscribed`](/dev/webhooks/events/email-unsubscribed)                   | A recipient uses an unsubscribe link                                   |
| [Widget](/dev/webhooks/events/widget)               | [`widget.conversation.started`](/dev/webhooks/events/widget-conversation-started) | A visitor starts a chat on your website                                |
|                                                     | [`widget.visitor.identified`](/dev/webhooks/events/widget-visitor-identified)     | A visitor gives their name, email or phone                             |
|                                                     | [`widget.keyword.matched`](/dev/webhooks/events/widget-keyword-matched)           | A visitor sends any message                                            |
| [Conversations](/dev/webhooks/events/conversations) | [`conversation.assigned`](/dev/webhooks/events/conversation-assigned)             | A conversation is given an owner                                       |
|                                                     | [`conversation.completed`](/dev/webhooks/events/conversation-completed)           | A conversation reaches `completed`                                     |
|                                                     | [`conversation.deleted`](/dev/webhooks/events/conversation-deleted)               | A conversation is deleted                                              |
|                                                     | [`conversation.outcome_set`](/dev/webhooks/events/conversation-outcome-set)       | A conversation is given an outcome                                     |

## Matching several events

A subscription holds a list of patterns, and an event is delivered if **any** of
them match.

| Pattern          | Matches                                                 |
| ---------------- | ------------------------------------------------------- |
| `record.created` | Exactly that event                                      |
| `record.*`       | `record.created`, `record.updated` and `record.deleted` |
| `email.*`        | All seven email events                                  |
| `conversation.*` | All four conversation events                            |
| `*`              | Everything, including events added in future            |

<Tip>
  `*` 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.
</Tip>

<Warning>
  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`.
</Warning>

## 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](/dev/events), 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](mailto: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}`](/dev/api-reference/records) sorted by the updated
timestamp.

<Note>
  Need webhooks from an import specifically? Tell us at
  [hello@custral.com](mailto: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.
</Note>

## Field values are ids

Select, status, relation and user values arrive as stored ids
(`["opt_3Kd8sM"]`), not labels. See
[Payload format](/dev/webhooks/payload#field-values-are-stored-values-not-display-values)
for how to resolve them.


## Related topics

- [Quickstart](/dev/webhooks/quickstart.md)
- [API Reference](/dev/api-reference/overview.md)
- [TypeScript SDK](/dev/sdks/typescript.md)
- [Real-time updates](/dev/realtime/overview.md)
- [Webhooks](/dev/webhooks/overview.md)
