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

# Webhook Events Reference

> The events webhooks receive, their payloads, and how to choose includes, filter updates and handle versions.

## Overview

A webhook has a dated `api_version`, such as `2026-11-12`, and subscribes to a list of `events`. Each delivery is an event envelope whose `data.object` is the public API model the event is about, in the webhook's `api_version`, exactly as the API returns it in that version.

This reference documents webhooks. Legacy webhooks, with `api_version: "legacy"`, send other events and payloads, documented in the [legacy webhook events reference](https://docs.getmateo.com/webhooks/webhook-events-reference.md).

Create a webhook by sending `events` to `POST /v1/webhooks`:

```json theme={null}
{
  "name": "CRM sync",
  "target_url": "https://example.com/hooks/hellomateo",
  "api_version": "2026-11-12",
  "events": ["contact.created", "contact.updated", "message.read"],
  "include": { "contact": ["identities", "channels"] },
  "filters": { "contact.updated": { "fields": ["full_name", "external_id"] } }
}
```

`api_version` defaults to the latest version. In the Web App, "Add Webhook" creates webhooks in the latest version, and the "Events" card on the webhook's page edits the version, events, includes and filters. `events` lists event types. Webhooks always send a `POST` with a JSON body, signed as described in [Verifying signatures](https://docs.getmateo.com/webhooks/setting-up-webhooks.md#verifying-signatures).

## The event envelope

```json theme={null}
{
  "id": "019a5f0e-6c1e-7b2a-9f3d-5d2c8e4a1b7f",
  "object": "event",
  "type": "contact.updated",
  "api_version": "2026-11-12",
  "timestamp": "2026-11-12T10:15:30.123Z",
  "data": {
    "object": {
      "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
      "display_name": "Ada King",
      "full_name": "Ada King",
      "...": "..."
    },
    "previous_attributes": {
      "full_name": "Ada Lovelace",
      "display_name": "Ada Lovelace"
    }
  }
}
```

* `id` is the event's ID. Every webhook that receives the event gets the same `id`; the `webhook-id` header identifies the delivery to one webhook.
* `type` is the event type.
* `api_version` is the webhook's version, which shapes `data.object`.
* `timestamp` is when the change happened.
* `data.object` is the model in the webhook's version, with the includes the webhook lists for that model.
* `data.previous_attributes` is present on update events only. See [Previous attributes](#previous-attributes).

The object is read shortly after the change commits, so it can already contain a later change made within the same few seconds. Deleted objects are the exception: they carry the object as it was before the deletion.

## Events

| Event | Model | Fires when |
| - | - | - |
| `contact.created` | `Contact` | A contact is created by an employee, an import, an integration, the API, or a first message from a new handle. |
| `contact.updated` | `Contact` | A contact's fields change. Identity and channel changes emit `contact_identity` and `contact_channel` events instead. |
| `contact.deleted` | `Contact` | A contact is deleted, or merged into another contact. |
| `contact_identity.created` | `ContactIdentity` | An identity, such as an email address or phone number, is added to a contact. |
| `contact_identity.updated` | `ContactIdentity` | An identity's health changes. |
| `contact_identity.deleted` | `ContactIdentity` | An identity is removed from its contact, or its value changes. |
| `contact_channel.created` | `ContactChannel` | A contact gets a channel: a handle, health, consent or message on a channel it had none on. |
| `contact_channel.updated` | `ContactChannel` | A channel's health, transactional opt-in or marketing subscription changes. Message counters do not emit it. |
| `contact_channel.deleted` | `ContactChannel` | A contact's handle, health, consent and messages on a channel are all cleared. |
| `conversation.created` | `Conversation` | A conversation is created by a first inbound or outbound message, a call, or the API. |
| `conversation.updated` | `Conversation` | A conversation's fields change, such as its subject, contact or spam flag. Status, assignee and inbox changes emit conversation events instead. |
| `conversation.assigned` | `ConversationEvent` | A conversation is assigned to an employee, a team or an AI agent. |
| `conversation.unassigned` | `ConversationEvent` | A conversation loses its assignee. |
| `conversation.closed` | `ConversationEvent` | A conversation is closed. |
| `conversation.reopened` | `ConversationEvent` | A closed conversation is opened again, by an employee, an automation or a new inbound message. |
| `conversation.inbox_changed` | `ConversationEvent` | A conversation moves to another inbox. |
| `conversation.tag_added` | `ConversationEvent` | A tag is added to a conversation. |
| `conversation.tag_removed` | `ConversationEvent` | A tag is removed from a conversation. |
| `message.received` | `ConversationEvent` | A message is received from a contact. |
| `message.submitted` | `ConversationEvent` | The channel provider accepted an outbound message for sending. |
| `message.sent` | `ConversationEvent` | The channel provider reports an outbound message as sent. |
| `message.sending_failed` | `ConversationEvent` | The channel provider could not send an outbound message. |
| `message.delivery_failed` | `ConversationEvent` | An outbound message could not be delivered to a recipient, for example because an email bounced. |
| `message.submission_failed` | `ConversationEvent` | The channel provider rejected an outbound message. |
| `message.read` | `ConversationEvent` | A recipient read an outbound message. |
| `comment.created` | `ConversationEvent` | An employee comments on a conversation. |
| `message_action.clicked` | `ConversationEvent` | A contact clicks a button or link of a message. |
| `appointment.created` | `Appointment` | An appointment is booked by a contact, created by an employee or the API, or synced from a connected calendar. |
| `appointment.updated` | `Appointment` | An appointment's fields or status change, for example when it is rescheduled, confirmed or cancelled. |
| `appointment.deleted` | `Appointment` | An appointment is deleted, or removed with its contact or calendar. |
| `deal.created` | `Deal` | A deal is created by an employee, an automation, an integration or the API. |
| `deal.updated` | `Deal` | A deal's fields or status change, for example when it moves to another stage, or is won or lost. |
| `deal.deleted` | `Deal` | A deal is deleted, or removed with its contact. |

The API reference lists every event under Webhook Events, with the schema of its payload.

Changes made by internal data repairs and by the import of a WhatsApp Business App's chat history do not emit events.

### Deleted objects

A `*.deleted` event carries the object as it was before the deletion, with the includes your webhook lists. Objects removed together with another object (an appointment or a deal removed with its contact, an appointment removed with its calendar, a contact merged into another contact) carry their own fields but no includes. A contact merged into another one has already lost its handles and address to the contact it was merged into.

`contact_identity.deleted` and `contact_channel.deleted` carry the identity or channel with the same `id` the contact's `identities` and `channels` include returned. An identity's `id` depends on its value, so changing an email address or phone number emits `contact_identity.deleted` for the old identity and `contact_identity.created` for the new one.

### Conversation events

The conversation and message events other than `conversation.created` and `conversation.updated` are the conversation's timeline, which `GET /v1/conversation-events` returns. Each carries the `ConversationEvent`, exactly as `GET /v1/conversation-events/{event_id}` returns it: its `type`, the `conversation_id`, the `actor` who caused it and the `resource` it concerns, such as the message, the tag or the assignee.

* The message events follow the message's progress as the API reports it: `message.submitted` once the provider's API accepted the message, `message.sent` once the provider reports it sent, and `message.read` once a recipient read it. `message.submission_failed`, `message.sending_failed` and `message.delivery_failed` report failures; the failure events carry `details.error` when the reason is known.
* hellomateo does not record the delivery to the device that WhatsApp providers report yet, and email providers report no sent status, so an email goes from `message.submitted` to `message.read`, or stays at `message.submitted`.
* Providers report statuses out of order and more than once. A message can emit the same status event again, or an earlier status after a later one.
* To receive the message or the conversation itself, include `message` or `conversation`: see [Include](#include).

## Previous attributes

Update events carry `data.previous_attributes`: the changed fields of the model and their old values, with the model's field names.

* `contact.updated`, `contact_identity.updated`, `contact_channel.updated`, `conversation.updated`, `appointment.updated` and `deal.updated` list every changed field except `updated_at`. `contact_channel.updated` also leaves out the message counters `messages_this_month` and `marketing_messages_this_month`, and changing only these does not emit it. `conversation.updated` also leaves out `status` and `inbox_id`, which conversation events report, and `latest_activity_created_at` and `unread`: changing only these, assigning the conversation, or marking it read or unread does not emit it. `contact.updated` also leaves out `is_blocked`, and blocking or unblocking a contact alone does not emit it.
* A change that only affects includes, such as an appointment's or deal's status record, emits an update event whose `previous_attributes` is empty. A contact's identities and channels emit their own events instead of `contact.updated`.
* `previous_attributes` holds the old values as the latest API version names and renders them. Timestamps are ISO 8601 strings in UTC.

## Include

`include` is keyed by model and lists the include values the model's Get route offers in the webhook's `api_version`:

| Model | Include values |
| - | - |
| `contact` | `identities`, `channels` |
| `conversation` | `contact`, `assignee` |
| `conversation_event` | `conversation`, `message` |
| `appointment` | `additional_fields`, `contact`, `status`, `calendar` |
| `deal` | `contact`, `status`, `assignee` |

The includes apply to every event carrying the model: `include: { "conversation_event": ["message"] }` adds the message to `message.received`, `message.read` and every other conversation event. An event that does not concern a message carries `message: null`.

## Filters

`filters` is keyed by an update event the webhook subscribes to. `fields` lists fields of the event's model; the webhook receives the event only when `previous_attributes` contains one of them:

```json theme={null}
{
  "events": ["deal.updated"],
  "filters": { "deal.updated": { "fields": ["status_key", "value"] } }
}
```

Filters apply to `*.updated` events only. An update that only changed includes has empty `previous_attributes` and never matches a filter.

## Versioning

A webhook receives every payload in its `api_version`. A new API version that changes a model changes the payload of the events carrying it in that version only; the API changelog lists the webhook events each change affects. To move a webhook to a newer version, update its `api_version`, or pick the version under "Events" on the webhook's page, after your receiver handles the changes up to that version. Include values the model does not offer in the new version are rejected.

New event types are added without a new API version. A webhook receives only the event types in its `events`: add new ones to receive them.

## Delivery guarantees

* Events are delivered at least once. Deduplicate on the `webhook-id` header, or on the envelope `id` per webhook.
* Events are not ordered. Compare `timestamp`, or the object's `updated_at`, to discard stale events.
* Failed deliveries are retried as described in [Retries](https://docs.getmateo.com/webhooks/setting-up-webhooks.md#retries).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.