The envelope
Every event has the same outer shape. Onlydata changes from one type to another.
All event types
Contact events carry the whole contact, so most integrations never need to call the API back. Conversation and message events carry ids only: read the conversation or its messages from the API when you need the content.
New event types are added over time. An endpoint subscribed to all events starts receiving them automatically. Answer
200 to any type you don’t handle.The contact object
Contact events include acontact object with the same fields as GET /v1/contacts/{id}:
propertiesholds your custom properties, keyed by the property’s key. Readkindto know whichvalue_*field holds the value.- File properties are left out. A file’s download link is temporary and would expire before a retried delivery arrives. Read the contact from the API to get a fresh link.
- The contact is a snapshot taken when Ciarem prepares the event, a moment after the change. If the contact changed twice in quick succession, both events can show the latest state. A retry sends the same snapshot again, not a fresh one.
Contact events
contact.created
A contact was added: by a teammate, by an import, by the API, by an integration such as HubSpot, or because a new person wrote in on a channel.contact.updated
A contact’s own fields (name, email, phone, city, and so on) or one of its custom properties changed.changed_fields lists what changed:
- A plain field appears by name:
"first_name","email". - A custom property appears as
properties.<key>:"properties.custom_budget". Each custom property that changes sends its owncontact.updatedevent.
contact.updated, contact.funnel_stage.changed, and contact.owner.changed.
contact.deleted
A contact was deleted. The contact no longer exists, so only its id is sent.contact.merged
Two contacts were merged.contact is the one that survives, with the merged result. merged_contact_id is the contact that was folded into it and no longer exists on its own: update any records on your side that still point at it.
contact.funnel_stage.changed
A contact moved to another funnel stage, whether a teammate moved it or the AI agent did. The new stage iscontact.funnel_stage_id; the one it left is previous_funnel_stage_id. Resolve stage ids to names with GET /v1/funnel-stages.
contact.owner.changed
A contact’s owner changed. The new owner iscontact.owner_id; the previous one is previous_owner_id. Either can be null, meaning the contact has, or had, no owner.
Conversation and message events
These four events share the same basedata:
conversation.created
A new conversation started.Ciarem waits a few seconds for the contact to be linked before sending this event, so
contact_id is almost always present. In the rare case it is still null, read the conversation from the API a little later. This short wait also means the first messages.inbound of a conversation can arrive before its conversation.created.conversation.assigned
A conversation was assigned, by a teammate taking it, by an admin, by your automatic assignment rules, or by the AI agent handing it over.assignee_id is the teammate, always set. team_id is the conversation’s team and can be null.
conversation.closed
A conversation was resolved, either by a teammate finishing the chat or by the AI agent closing it. The body is the basedata above.
messages.inbound
A customer sent a message. The event tells you which conversation has something new; it does not contain the message itself.WhatsApp form events
whatsapp_flow.completed
A customer submitted a WhatsApp form. It fires once per submission, after Ciarem saved the answers.answersis keyed by the form’s internal field names, not by the question text. To read the values by property, useoutcome.saved(the contact fields and properties the answers were written to) and read the contact.outcome.errorslists answers that could not be saved, for example an email that belongs to another contact.- A booking form also carries
booking_id,start_at,end_at,meeting_url, andattendee_emailinoutcome. - A photo or document question’s answer is a list of
{"file_name", "mime_type"}, andoutcome.filesgives its state:{"q3": {"status": "pending"}}. The event fires before the files are stored. When the question saves to a file property, the stored files arrive with thecontact.updatedevent that follows. conversation_idcan benullwhen the form was sent with a template outside a conversation.
The test event
A test sent from the dashboard or withPOST /v1/webhook-endpoints/{id}/test looks like a real event, with two differences:
- Its
idstarts withevt_test_, so you can tell it apart and skip any real side effects. - If you don’t choose an event type, its
typeistestand itsdatais empty:
GET /v1/webhook-event-samples returns the sample body of every type, which is handy for writing tests of your own handler.
Compatibility
Webhook bodies follow the same rule as the rest of the API: changes are additive. New event types, new fields insidedata, and new values in string fields can appear at any time. Read the fields you need, ignore the rest, and check the changelog when something looks different.