Delivery Modes
Webhooks support two delivery modes depending on your setup:Hosted Mode
No server needed. Events are delivered via SSE stream or polling. Perfect for bots, scripts, and local apps.Create a webhook without a
url to use hosted mode.Custom Mode
Events are sent as
POST requests to your server. Requires a publicly accessible HTTPS endpoint.Create a webhook with a url to use custom mode.| Hosted | Custom | |
|---|---|---|
| Setup | No server required | HTTPS endpoint required |
| Real-time delivery | SSE stream (GET /stream) | HTTP POST to your URL |
| Event history | 24h stored events (GET /events) | Fire-and-forget |
| Best for | Bots, scripts, local dev | Production servers |
How It Works
- Hosted Mode
- Custom Mode
Create a hosted webhook
Call
POST /v2/webhooks without a url. You’ll receive a webhook_id, stream_url, and events_url.Event Types
| Subscription filter | Emitted event.type | Description |
|---|---|---|
message_received | "message" | A new LinkedIn message was received |
accepted_invitation | "accepted_invitation" | One or more connection requests you sent were accepted |
all | — | Subscribe to all event types |
By default, webhooks listen to all event types. You can filter specific events when creating the webhook by passing an
events array.In addition to subscribed events, every webhook also receives
disconnection events — this is not filterable. They fire whenever the underlying LinkedIn session expires so you can react (re-auth, alert, etc.).Event Payload Format
All events share the same wrapper structure. Note that the outertimestamp is when the API emitted the event, while event.timestamp is when the underlying LinkedIn activity happened — they differ slightly (latency).
Message Received
Triggered in real-time when a new LinkedIn message is received via the LinkedIn realtime stream (sub-second latency). The inner eventtype is "message".
| Field | Type | Description |
|---|---|---|
event.message_text | string | Plain text content of the message |
event.sender.name | string | Display name of the sender |
event.sender.profile_url | string | LinkedIn URL of the sender (URN-style, not vanity) |
event.conversation.participants | array | All participants of the thread, sender included |
event.conversation.is_group_chat | boolean | true if 3+ participants |
event.conversation.unread_count | integer | Number of unread messages in the thread |
event.reply_to | object? | Only present when the message is a reply. Contains message_text, sender_name, sender_profile_url, timestamp of the original message. Omitted (not null) for non-reply messages. |
event.metadata.entity_urn | string | LinkedIn URN of the message |
event.metadata.conversation_urn | string | LinkedIn URN of the conversation thread |
event.metadata.delivered_at | integer | Unix milliseconds delivery time |
event.metadata.read_status | boolean | Whether the conversation is marked read |
Accepted Invitation
Triggered when one or more connection requests you sent are accepted. Detected by a background worker that polls the connections list every ~6 hours and diffs against the previously stored snapshot. The payload batches all new connections detected in the same check.| Field | Type | Description |
|---|---|---|
event.new_connections | array | Profiles that accepted your connection request since the last check |
event.new_connections[].profile_url | string | LinkedIn vanity URL of the connection |
event.new_connections[].name | string | Display name |
event.new_connections[].job_title | string | null | Headline / current role as shown on LinkedIn |
event.new_connections[].profile_picture | string | null | CDN URL of the profile picture (may expire) |
event.count | integer | Length of new_connections |
When you create a webhook subscribed to
accepted_invitation (or all), the API performs an initial sync of your existing connections in the background. The first detection check then runs ~6 hours later and every 6 hours thereafter.Disconnection
Triggered when the account’s LinkedIn session expires, the cookie is invalidated, or the user logs out. Sent to every webhook attached to the account regardless of subscription filters.| Field | Type | Description |
|---|---|---|
event.reason | string | Human-readable cause (e.g. "Token expired (detected after 10 reconnections)", "LinkedIn cookie invalid or expired") |
Architecture
Unlike the V1 webhook system which required creating a separate “webhook account”, the V2 system works with your existing accounts:- V2 accounts are created via
POST /v2/login(profiles, messages, network, etc.) - Webhooks are configurations that attach to those accounts
- One account can have multiple webhooks with different event filters
- Starting/stopping monitoring is done per webhook