Webhooks

Messaging events

Seven events that fire whenever a message or chat changes state on a connected LinkedIn account, with full message content delivered in every message payload.

Before you start

You need an API key and a connected LinkedIn account: account_ids is required and non-empty. Read your ids with GET /v1/accounts; if it is empty, start with the Quickstart.

Build against the envelope: the top-level event plus data, which always carries account_id and occurred_at. Treat new data fields as additive.

Quick reference

EventDescription
message.receivedA new inbound message arrived in the account's inbox.
message.readA message in the conversation was read.
message.reactionA reaction was added to a message.
message.editedA message was edited; the payload carries the updated text.
message.deletedA message was deleted from the conversation.
chat.updatedA chat's container state changed (e.g. archived, muted, read-state). Opt-in.
chat.deletedA chat thread was deleted. Opt-in.

The five message.* events share one data shape; the two chat.* events carry a minimal chat shape. All seven work on any paid seat. The chat.* events are opt-in and must be named in events:

curl -X POST https://api.curviate.com/v1/webhooks \
  -H "Authorization: Bearer cvt_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "messaging",
    "request_url": "https://hooks.example.com/curviate",
    "account_ids": ["acc_YOUR_ACCOUNT_ID"],
    "events": [
      "message.received",
      "message.read",
      "message.reaction",
      "message.edited",
      "message.deleted"
    ]
  }'

message.received

Fired when an inbound message arrives, classic message or InMail. Inbound only: a message the account sent never fires it, and there is no outbound push event. On the read path, is_delivered on the message resource is set on a successful send; it may reflect send success rather than confirmed delivery.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "message.received",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "message.received",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "message_id":  "msg_YOUR_MESSAGE_ID",
    "text":        "Hello, I saw your profile and wanted to connect.",
    "sender": {
      "name":        "Alex Jordan",
      "profile_url": "https://www.linkedin.com/in/alexjordan",
      "provider_id": "urn:li:member:123456789"
    },
    "attachments": [],
    "occurred_at": "2026-05-28T14:22:05.000Z"
  },
  "delivered_at": "2026-05-28T14:22:06.789Z"
}

Notable fields

  • sender: name, profile_url, and provider_id (the LinkedIn member URN).
  • attachments: empty when there are none; each item carries id, type, url, mimetype, and size.
  • chat_id: the conversation, the same across every event in the thread.
  • motion_id, enrollment_id: on every message.* event, set when the chat belongs to a Motion's Enrollment on this account, null otherwise. Use motion_ids on the webhook to receive only those.

message.read

Fired when a message is read. Adds read_by when the reader is disclosed; otherwise the field is absent, not null.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "message.read",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "message.read",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "message_id":  "msg_YOUR_MESSAGE_ID",
    "text":        "Hello, I saw your profile and wanted to connect.",
    "sender": {
      "name":        "Alex Jordan",
      "profile_url": "https://www.linkedin.com/in/alexjordan",
      "provider_id": "urn:li:member:123456789"
    },
    "read_by":     "urn:li:member:987654321",
    "attachments": [],
    "occurred_at": "2026-05-28T14:25:00.000Z"
  },
  "delivered_at": "2026-05-28T14:25:01.012Z"
}

message.reaction

Fired when a reaction is added to a message. reaction carries the value, for example an emoji string.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "message.reaction",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "message.reaction",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "message_id":  "msg_YOUR_MESSAGE_ID",
    "text":        "Thanks for reaching out, happy to connect.",
    "reaction":    "👍",
    "sender": {
      "name":        "Alex Jordan",
      "profile_url": "https://www.linkedin.com/in/alexjordan",
      "provider_id": "urn:li:member:123456789"
    },
    "attachments": [],
    "occurred_at": "2026-05-28T14:26:30.000Z"
  },
  "delivered_at": "2026-05-28T14:26:31.234Z"
}

message.edited

Fired when a message is edited. text is the post-edit content; the original is not included.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "message.edited",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "message.edited",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "message_id":  "msg_YOUR_MESSAGE_ID",
    "text":        "Thanks for reaching out, let's schedule a call.",
    "sender": {
      "name":        "Jordan Lee",
      "profile_url": "https://www.linkedin.com/in/jordanlee",
      "provider_id": "urn:li:member:987654321"
    },
    "attachments": [],
    "occurred_at": "2026-05-28T14:27:45.000Z"
  },
  "delivered_at": "2026-05-28T14:27:46.789Z"
}

message.deleted

Fired when a message is deleted. text may be absent; do not depend on it.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "message.deleted",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "message.deleted",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "message_id":  "msg_YOUR_MESSAGE_ID",
    "sender": {
      "name":        "Jordan Lee",
      "profile_url": "https://www.linkedin.com/in/jordanlee",
      "provider_id": "urn:li:member:987654321"
    },
    "attachments": [],
    "occurred_at": "2026-05-28T14:28:00.000Z"
  },
  "delivered_at": "2026-05-28T14:28:01.345Z"
}

chat.updated

Fired when a thread's state changed, for example archived, muted, or read-state. It is about a chat thread, not a message. Opt-in. account_id and chat_id are always present; look up the current thread state on your side.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "chat.updated",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "chat.updated",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "occurred_at": "2026-05-28T14:30:00.000Z"
  },
  "delivered_at": "2026-05-28T14:30:01.000Z"
}

chat.deleted

Fired when a chat thread was deleted. Opt-in. account_id and chat_id (the deleted thread) are always present.

{
  "id":          "wdl_YOUR_DELIVERY_ID",
  "webhook_id":  "wh_YOUR_WEBHOOK_ID",
  "event":       "chat.deleted",
  "data": {
    "account_id":  "acc_YOUR_ACCOUNT_ID",
    "event":       "chat.deleted",
    "chat_id":     "chat_YOUR_CHAT_ID",
    "occurred_at": "2026-05-28T14:31:00.000Z"
  },
  "delivered_at": "2026-05-28T14:31:01.000Z"
}

Field remapping

Has no effect on delivery

The data array is accepted, stored, and echoed back on read, but it does not change what is delivered. The payload is always the fixed shape above. Do not build a handler that depends on it.

On this source the array is not validated, so a typo is accepted silently (the user source does validate). The 27 keys intended for messaging:

account_id        account_type      account_info
chat_id           timestamp         webhook_name
message_id        message           mentions
reaction          reaction_sender   read_by
sender            is_sender         attendees
attachments       subject           provider_chat_id
provider_message_id  is_event       chat_pinned
quoted            is_forwarded      chat_content_type
message_type      is_group          folder

is_sender is listed but never delivered: data.is_sender is always undefined.

Errors you may hit

CodeHTTPCauseFix
INVALID_REQUEST400An event in events[] does not belong to the messaging source, request_url is not HTTPS, or a field is missing.Read the message, it names the field.
ACCOUNT_NOT_FOUND404An acc_... in account_ids is not owned by this tenant.Read your ids from GET /v1/accounts.
UNAUTHORIZED401Missing or invalid API key.Send Authorization: Bearer cvt_live_<key>.
PAYMENT_REQUIRED402No active subscription.Add a seat in the dashboard.
RATE_LIMIT_TENANT429Tenant quota exceeded.Honour the Retry-After header.

Full envelope shapes are in the error reference.

Next steps