What's in the payload
Beginner-friendly walkthrough of the JSON ConnectSafely sends to your webhook URL — every field explained, with an example you can copy and a tip on deduplicating retries.
When something happens, ConnectSafely sends your URL a single POST request:
- HTTP method:
POST - Content type:
application/json - Body: the JSON described below
This page walks through what's in that body so you know exactly what your code is working with.
A real example
Here's what your endpoint will see for a message.received event:
{
"event": "message.received",
"object": "event",
"data": {
"id": "urn:li:messagingMessage:2-...",
"message_urn": "urn:li:messagingMessage:2-...",
"account_id": "65fa...c0",
"provider": "LINKEDIN",
"sender": {
"id": "urn:li:fsd_profile:ACoAA...",
"name": "Jane Doe",
"profile_url": null,
"profile_picture": "https://media.licdn.com/dms/image/..."
},
"body": "Hey — saw your post about pipeline automation.",
"timestamp": "2026-05-12T14:33:21.000Z",
"conversation_id": "urn:li:messagingThread:2-...",
"attachments": []
}
}Heads up: this is the same shape Unipile uses for message webhooks. Tooling built against Unipile usually works against ConnectSafely with no changes.
The outer wrapper
Every webhook body has the same three top-level fields, regardless of event type:
| Field | What it is |
|---|---|
event | A string saying which kind of thing happened, e.g. message.received |
object | Always the literal string "event". You can ignore it. |
data | The interesting part — fields specific to the event. |
So in your code, you'll typically check body.event first, then read body.data.
What's inside data for message.received
| Field | Plain English |
|---|---|
id | Unique ID for this message. Same value as message_urn. |
message_urn | The LinkedIn message ID (a URN that starts with urn:li:messagingMessage:). Use this to dedupe. |
account_id | Which of your connected ConnectSafely accounts received the message. |
provider | Always "LINKEDIN" for now. |
sender.id | The sender's LinkedIn profile URN, when LinkedIn gives us one. Can be empty. |
sender.name | The sender's display name, e.g. "Jane Doe". |
sender.profile_url | Reserved for future use — currently always null. |
sender.profile_picture | URL of the sender's avatar, if available. |
body | The message text itself. Empty string if it had no text body. |
timestamp | When LinkedIn says the message was sent, as an ISO 8601 string. |
conversation_id | The LinkedIn thread URN — useful if you want to fetch the rest of the conversation later. |
attachments | Reserved. Currently always []. We'll populate it when we add attachment support. |
InMail and regular DMs both arrive as
message.received. If you need to tell them apart, you can check the URN format ofmessage_urn, or call Get conversation messages withconversation_idto look at the thread metadata.
HTTP headers worth knowing
ConnectSafely also sets a few request headers your endpoint can inspect:
| Header | Why you'd look at it |
|---|---|
Content-Type | Always application/json — your framework will parse the body for you. |
User-Agent | Always ConnectSafely-Webhooks/1.0. Useful for allow-listing at a firewall / WAF. |
X-Webhook-Event | The same string as body.event. Lets you route by event type without parsing the body first. |
X-Webhook-Timestamp | Unix time (seconds) when ConnectSafely generated the request. Drop very old values to limit replay. |
X-Webhook-Signature | sha256=<hex> — the security signature. Check this before trusting the body. How. |
If you configured an outbound auth header (Bearer / API key / Basic) when creating the webhook, that header is added too.
"I think I got the same message twice"
That can happen — for example, if your server returns a 500 for one request, ConnectSafely will retry and the message will arrive again. Same message → same message_urn.
Use message_urn as a dedup key:
// Pseudocode — works with any key/value store: Redis, your DB, an in-memory Set
if (await store.has(payload.data.message_urn)) {
return res.status(200).end(); // already processed
}
await store.add(payload.data.message_urn);
// ... do the real work ...A 5-minute TTL on the key is usually plenty — retries finish within ~70 seconds.
What to read next
- Security & auth — verify the signature, set outbound headers
- Retries & logs — what happens when your endpoint fails
- Open the dashboard
Webhooks
Get a real-time HTTP notification on your own server whenever a LinkedIn message or InMail arrives. Beginner-friendly guide with examples in Node.js and Python.
Security & Auth
Plain-English guide to verifying ConnectSafely webhook signatures so attackers can't fake events. Includes ready-to-run Node.js and Python examples.
