Webhook events

Webhook event: message.received

The payload Cooledge sends when a customer or an unknown sender texts or emails the business.

Updated 11/10/2026

message.received

Sent when a customer or an unknown sender texts or emails the business and the message lands in Messages. Delivered to your webhook subscriptions. Deliveries are at-least-once, so dedupe on X-Cooledge-Event-Id.

It is not instant. Cooledge waits until an email's attachments (or a picture message's media) are stored, so the event lists every file, then sends it on a delivery run every few minutes. Expect it usually within a few minutes of the message arriving. Do not build anything that needs it in real time.

Only messages that arrive while a subscription to message.received exists are sent, so subscribing does not replay older messages. Calls and voicemails have their own events. A website chat that creates a lead is covered by lead.created instead. Internal team comments never fire it.

Subscribing to this event through the API needs the messages:read scope. See the events index.

Envelope

Every event shares the flat envelope: event_type, version ("v1"), event_id, business_id, occurred_at, plus the fields below. See the events overview for the envelope detail and webhooks for signature verification and delivery semantics. Here occurred_at is when the message arrived.

Fields

FieldTypeNotes
messageobjectThe message.
message.idstringUUID of the message. The same id the messages endpoint returns.
message.directionstringAlways "in" for this event.
message.channelstring"sms" or "email".
message.subjectstring or nullThe email subject. Null for a text.
message.textstring or nullThe plain text body.
message.htmlstring or nullThe email's HTML. Null for a text. This is the sender's content: treat it as untrusted and sanitise it before you render it.
message.fromstring or nullThe sender's phone number or email address.
message.tostring or nullThe business number or address it was sent to.
message.ccarray of stringsEmail CC addresses. Empty for a text.
message.atstringISO 8601 timestamp for when the message arrived.
message.attachmentsarrayThe files on the message, each { filename, content_type, size } with content_type and size (in bytes) possibly null. Inline email images are left out. Empty when there are none.
conversationobject or nullThe conversation the message is in: { id, state, is_unmatched }. state is one of open, snoozed, archived, trashed or spam. is_unmatched is true when the sender is not matched to a customer.
customerobject or nullThe matched customer. Null for an unknown sender.
customer.idstringUUID of the customer.
customer.namestring or nullThe customer's display name.
customer.emailstring or nullThe customer's saved email, which may differ from the address the message came from.
customer.phonestring or nullThe customer's saved phone, which may differ from the number the message came from.
contactobject or nullThe customer's contact the message came from, when it came from one. Null otherwise.
contact.idstringUUID of the contact.
contact.namestring or nullThe contact's first and last name.
sender_addressstring or nullAn unknown sender's phone number or email address. Null when the conversation is matched to a customer.

The payload is built when the event is sent, a few minutes after the message arrived, so conversation.state and the customer details are as they were then.

Example

{
  "version": "v1",
  "event_type": "message.received",
  "event_id": "b4e7d2a9-1c58-4f36-8e0b-6a3d9c2f7e15",
  "business_id": "a7f3c9d1-2e84-4b6f-8c01-5d9e2a1f7b30",
  "occurred_at": "2026-10-11T03:21:00.000Z",
  "message": {
    "id": "4a8e2c71-9b06-4d3f-a5e8-7c1d0f6b2a93",
    "direction": "in",
    "channel": "email",
    "subject": "Aircon still making a noise",
    "text": "Hi, can you come Thursday instead? I have attached a video of the noise.",
    "html": "<p>Hi, can you come Thursday instead? I have attached a video of the noise.</p>",
    "from": "priya.nair@example.com",
    "to": "hello@coolairhvac.com.au",
    "cc": [],
    "at": "2026-10-11T03:21:00.000Z",
    "attachments": [
      { "filename": "noise.mp4", "content_type": "video/mp4", "size": 4183920 }
    ]
  },
  "conversation": {
    "id": "8f3a1c52-6d47-4b9e-a210-5c7e9d2b4f18",
    "state": "open",
    "is_unmatched": false
  },
  "customer": {
    "id": "c4a9e1b2-7d3f-4a8c-9e2b-1f6a0d5c3b7e",
    "name": "Priya Nair",
    "email": "priya.nair@example.com",
    "phone": "+61498765432"
  },
  "contact": null,
  "sender_address": null
}

To reply, write a draft with POST /v1/drafts using conversation.id and, for an email, message.id as reply_to_message_id. See webhooks for signature verification and delivery semantics. See all events for the full list.

Need a hand with an integration? Contact support