Skip to main content

The envelope

Every event has the same outer shape. Only data 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 a contact object with the same fields as GET /v1/contacts/{id}:
Three things to know:
  • properties holds your custom properties, keyed by the property’s key. Read kind to know which value_* 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 own contact.updated event.
Funnel stage and owner changes are not reported here. They have their own events, below. One edit that changes a name, the stage, and the owner at once sends three events: 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 is contact.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 is contact.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 base data:

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 base data above.

messages.inbound

A customer sent a message. The event tells you which conversation has something new; it does not contain the message itself.
To read what was said, fetch the conversation’s messages:
Messages your team or the AI agent send do not fire an event.

WhatsApp form events

whatsapp_flow.completed

A customer submitted a WhatsApp form. It fires once per submission, after Ciarem saved the answers.
  • answers is keyed by the form’s internal field names, not by the question text. To read the values by property, use outcome.saved (the contact fields and properties the answers were written to) and read the contact.
  • outcome.errors lists 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, and attendee_email in outcome.
  • A photo or document question’s answer is a list of {"file_name", "mime_type"}, and outcome.files gives 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 the contact.updated event that follows.
  • conversation_id can be null when the form was sent with a template outside a conversation.

The test event

A test sent from the dashboard or with POST /v1/webhook-endpoints/{id}/test looks like a real event, with two differences:
  • Its id starts with evt_test_, so you can tell it apart and skip any real side effects.
  • If you don’t choose an event type, its type is test and its data is empty:
When you do choose a type, the test carries sample data in exactly the shape a real event has. 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 inside data, 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.