Overview
A webhook has a datedapi_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.
Create a webhook by sending events to POST /v1/webhooks:
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.
The event envelope
idis the event’s ID. Every webhook that receives the event gets the sameid; thewebhook-idheader identifies the delivery to one webhook.typeis the event type.api_versionis the webhook’s version, which shapesdata.object.timestampis when the change happened.data.objectis the model in the webhook’s version, with the includes the webhook lists for that model.data.previous_attributesis present on update events only. See Previous attributes.
Events
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 thanconversation.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.submittedonce the provider’s API accepted the message,message.sentonce the provider reports it sent, andmessage.readonce a recipient read it.message.submission_failed,message.sending_failedandmessage.delivery_failedreport failures; the failure events carrydetails.errorwhen 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.submittedtomessage.read, or stays atmessage.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
messageorconversation: see Include.
Previous attributes
Update events carrydata.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.updatedanddeal.updatedlist every changed field exceptupdated_at.contact_channel.updatedalso leaves out the message countersmessages_this_monthandmarketing_messages_this_month, and changing only these does not emit it.conversation.updatedalso leaves outstatusandinbox_id, which conversation events report, andlatest_activity_created_atandunread: changing only these, assigning the conversation, or marking it read or unread does not emit it.contact.updatedalso leaves outis_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_attributesis empty. A contact’s identities and channels emit their own events instead ofcontact.updated. previous_attributesholds 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:
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:
*.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 itsapi_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-idheader, or on the envelopeidper webhook. - Events are not ordered. Compare
timestamp, or the object’supdated_at, to discard stale events. - Failed deliveries are retried as described in Retries.