Webhooks

Event reference

Every event Curviate can deliver to your webhook endpoint, grouped by source.

Before you start

Reading the catalogue needs only an API key. Subscribing needs a connected LinkedIn account for messaging, user, and post webhooks, because account_ids is required and non-empty there. Read your ids with GET /v1/accounts; if it is empty, start with the Quickstart.

Quick reference

not_realtime means a delay; no_longer_realtime means not delivered. Message events are inbound only.

EventSourceAvailabilityDescription
message.receivedmessagingReal-timeInbound message or InMail arrived.
message.readmessagingReal-timeA message was read.
message.reactionmessagingReal-timeReaction added.
message.editedmessagingReal-timeMessage edited.
message.deletedmessagingReal-timeMessage deleted.
chat.updatedmessagingReal-time · opt-inThread archived, muted, or read-state changed.
chat.deletedmessagingReal-time · opt-inThread deleted.
connection.acceptedusernot_realtime (~8h)Your invitation was accepted.
connection.newusernot_realtime (~4h) · opt-inAny new connection.
account.createdaccount_statusReal-timeFirst successful link.
account.connectedaccount_statusReal-timeHealthy, including a reactivated account.
account.syncedaccount_statusno_longer_realtimeA sync cycle completed.
account.reconnectedaccount_statusReal-timeRe-authorized in place.
account.reconnect_neededaccount_statusReal-timeNeeds re-authentication.
account.restrictedaccount_statusnot_realtimeConnected, but some actions fail.
account.creation_failedaccount_statusno_longer_realtimeFirst link attempt failed.
account.disconnectedaccount_statusReal-timeDisconnected; cannot act.
account.erroraccount_statusReal-timeSync error, not a credentials issue.
account.pausedaccount_statusno_longer_realtimeSync paused, not an error.
account.connectingaccount_statusno_longer_realtimeA link is in progress.
account.permission_revokedaccount_statusno_longer_realtimeLinkedIn permissions withdrawn.
account.initial_sync.runningaccount_statusReal-time · opt-inHistory backfill started.
account.initial_sync.completedaccount_statusReal-time · opt-inBackfill done; history queryable.
account.initial_sync.failedaccount_statusReal-time · opt-inBackfill failed, with a reason.
post.publishedpostReal-timeA scheduled post was published.
post.publish_failedpostReal-timeA scheduled post could not be published.

Delivery availability

GET /v1/webhooks/events returns the live catalogue, with an availability field:

ValueMeaning
omitted, or realtime

Real time. The three account.initial_sync.* events carry realtime explicitly.

not_realtime

Delivered, not sub-second. connection.accepted and connection.new are polled (hours). account.restricted is polled (up to ~24h) unless a connect observes it, or one LinkedIn product on the account stops working.

no_longer_realtime

Not delivered. For most, read GET /v1/accounts/{account_id} instead.

The catalogue is wider than what you can subscribe to

GET /v1/webhooks/events returns 29 events across 6 sources. This article documents the 26 across 4 sources you can subscribe to. Create rejects the other sources with 400 INVALID_REQUEST; the create schema is the authority.

Five events are no longer delivered

account.synced, account.creation_failed, account.connecting, account.paused, and account.permission_revoked stay in the create enum: subscribing returns 201 and no delivery ever arrives. If a subscription is silent, check its events here first.

Default and opt-in events

Omitting events on create subscribes to the source's default set:

SourceDefault events (when events is omitted)
messagingmessage.received
userconnection.accepted
account_statusthe 12 lifecycle events (not the three account.initial_sync.*)
postpost.published and post.publish_failed

chat.updated, chat.deleted, connection.new, and the account.initial_sync.* events are never auto-subscribed; name them in events.

Payload structure

Every delivery shares the same outer envelope:

{
  "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, how can I help?",
    "sender": {
      "name":        "Alex Jordan",
      "profile_url": "https://www.linkedin.com/in/alexjordan",
      "provider_id": "urn:li:member:123456789"
    },
    "attachments": [],
    "occurred_at": "2026-05-28T09:15:00.000Z"
  },
  "delivered_at": "2026-05-28T09:15:01.234Z"
}

id (wdl_...) changes on every retry. data always carries account_id, event, and occurred_at: deduplicate on those three. Message and connection events carry their content in data; account-status events carry state only; post events carry ids, timestamps, and a failure code, never the post text or media.

When an account needs re-authentication (account.reconnect_needed), the next call is POST /v1/auth/intent with its account_id.

Errors you may hit

CodeHTTPCauseFix
INVALID_REQUEST400An event outside the chosen source, or an unknown source.The message lists the accepted values.
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>.
RATE_LIMIT_TENANT429Tenant quota exceeded.Honour the Retry-After header.

Full envelope shapes are in the error reference.

Next steps